diff --git a/components/Card3D.js b/components/Card3D.js
new file mode 100644
index 0000000..47fe808
--- /dev/null
+++ b/components/Card3D.js
@@ -0,0 +1,530 @@
+/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
+import { useState } from 'react';
+import { ManaCost } from './ManaSymbols';
+import { VOCAB } from '../lib/collection-vocabulary.js';
+
+export default function Card3D({ card, viewMode, getRarityLabel, getRarityColor, useManaSvg = false }) {
+
+ const handleMouseMove = (e) => {
+ const rect = e.currentTarget.getBoundingClientRect();
+ setCardPosition({
+ x: rect.left,
+ y: rect.top,
+ width: rect.width,
+ height: rect.height
+ });
+ };
+
+ const handleMouseLeave = () => {
+ setIsHovered(false);
+ };
+
+ const handleMouseEnter = () => {
+ setIsHovered(true);
+ };
+
+ const getTCGColor = (game) => {
+ const colors = {
+ 'MTG': '#8B5CF6', // purple
+ 'Pokemon': '#3B82F6', // blue
+ 'Lorcana': '#EC4899' // pink
+ };
+ return colors[game] || '#6B7280';
+ };
+
+ const getSetColor = (setName) => {
+ // Generate a consistent color based on set name
+ const hash = setName.split('').reduce((a, b) => {
+ a = ((a << 5) - a) + b.charCodeAt(0);
+ return a & a;
+ }, 0);
+ const hue = Math.abs(hash) % 360;
+ return `hsl(${hue}, 70%, 60%)`;
+ };
+
+ const formatCurrency = (amount) => {
+ return new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD'
+ }).format(amount);
+ };
+
+ const getRarityGradient = (rarity) => {
+ const gradients = {
+ common: 'from-gray-400 to-gray-500',
+ uncommon: 'from-green-400 to-emerald-500',
+ rare: 'from-yellow-400 to-orange-500',
+ mythic: 'from-yellow-400 to-orange-500',
+ holographic: 'from-red-400 to-pink-500',
+ enchanted: 'from-purple-400 to-indigo-500',
+ ultra: 'from-blue-400 to-cyan-500'
+ };
+ return gradients[rarity] || 'from-gray-400 to-gray-500';
+ };
+
+ const getRarityGlow = (rarity) => {
+ const glows = {
+ common: '0 0 15px rgba(156, 163, 175, 0.4)',
+ uncommon: '0 0 15px rgba(34, 197, 94, 0.4)',
+ rare: '0 0 20px rgba(251, 191, 36, 0.5)',
+ mythic: '0 0 20px rgba(255, 215, 0, 0.4), 0 0 40px rgba(255, 215, 0, 0.2), 0 0 60px rgba(255, 215, 0, 0.1)',
+ holographic: '0 0 25px rgba(239, 68, 68, 0.6)',
+ enchanted: '0 0 20px rgba(168, 85, 247, 0.4), 0 0 40px rgba(168, 85, 247, 0.2), 0 0 60px rgba(168, 85, 247, 0.1)',
+ ultra: '0 0 25px rgba(59, 130, 246, 0.6)'
+ };
+ return glows[rarity] || '0 0 15px rgba(156, 163, 175, 0.4)';
+ };
+
+ if (viewMode === 'list') {
+ return (
+
+
+
+
+
+ {card.name}
+
+
+ {card.set_name} • {getRarityLabel(card.rarity)}
+
+
+ {card.oracle_text || card.card_type}
+
+
+
+
+ {formatCurrency(card.current_price || 0)}
+
+
+ {getRarityLabel(card.rarity)}
+
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Card Container with proper ratio and rarity glow */}
+
+ {/* Particle Effects for All Rarities */}
+ {card.rarity !== 'common' && (
+
+ {/* Get particle count and colors based on rarity */}
+ {(() => {
+ const rarityConfig = {
+ 'mythic': {
+ particleCount: 16,
+ sparkleCount: 10,
+ particleColor: '#FFD700',
+ sparkleColor: '#FFA500',
+ glowColor: '#FFD700'
+ },
+ 'enchanted': {
+ particleCount: 14,
+ sparkleCount: 8,
+ particleColor: '#A855F7',
+ sparkleColor: '#EC4899',
+ glowColor: '#A855F7'
+ },
+ 'rare': {
+ particleCount: 10,
+ sparkleCount: 6,
+ particleColor: '#3B82F6',
+ sparkleColor: '#60A5FA',
+ glowColor: '#3B82F6'
+ },
+ 'uncommon': {
+ particleCount: 6,
+ sparkleCount: 4,
+ particleColor: '#10B981',
+ sparkleColor: '#34D399',
+ glowColor: '#10B981'
+ }
+ };
+
+ const config = rarityConfig[card.rarity];
+ if (!config) return null;
+
+ return (
+ <>
+ {/* Floating Particles - Edge framing */}
+
+ {[...Array(config.particleCount)].map((_, i) => {
+ // Position particles around the card edges
+ let left, top;
+ const edgeIndex = i % 4; // 4 edges
+ const positionOnEdge = Math.floor(i / 4);
+
+ if (edgeIndex === 0) {
+ // Top edge
+ left = `${10 + (positionOnEdge * 20)}%`;
+ top = '2%';
+ } else if (edgeIndex === 1) {
+ // Right edge
+ left = '98%';
+ top = `${10 + (positionOnEdge * 20)}%`;
+ } else if (edgeIndex === 2) {
+ // Bottom edge
+ left = `${10 + (positionOnEdge * 20)}%`;
+ top = '98%';
+ } else {
+ // Left edge
+ left = '2%';
+ top = `${10 + (positionOnEdge * 20)}%`;
+ }
+
+ return (
+
+ );
+ })}
+
+
+ {/* Sparkle Effects - Corner highlights */}
+
+ {[...Array(config.sparkleCount)].map((_, i) => {
+ // Position sparkles in the corners and edge centers
+ let left, top;
+ if (i < 2) {
+ // Top corners
+ left = i === 0 ? '5%' : '95%';
+ top = '5%';
+ } else if (i < 4) {
+ // Bottom corners
+ left = i === 2 ? '5%' : '95%';
+ top = '95%';
+ } else if (i < 6) {
+ // Edge centers
+ left = i === 4 ? '50%' : '50%';
+ top = i === 4 ? '5%' : '95%';
+ } else {
+ // Side centers
+ left = i === 6 ? '5%' : '95%';
+ top = '50%';
+ }
+
+ return (
+
+ );
+ })}
+
+
+ {/* Rarity Aura - Blend with overall glow */}
+
+ >
+ );
+ })()}
+
+ )}
+ {/* Card Image */}
+
+ {card.image_url ? (
+

{
+ e.target.style.display = 'none';
+ e.target.nextSibling.style.display = 'flex';
+ }}
+ />
+ ) : null}
+
+ {/* Card Back placeholder when no image */}
+
+
+
+
+
+ {/* Owned Quantity Chip - Only show if owned */}
+ {card.quantity > 0 && (
+
+ )}
+
+ {/* Favorite Button */}
+
+
+
+
+
+ {/* Hover Details Panel */}
+ {isHovered && (
+
+
+ {/* Card Title */}
+
+
+ {/* Top Section - TCG, Set, Type, HP in 2x2 grid */}
+
+
+
+
Set
+
{card.set_name}
+
+ {card.type && (
+
+
Type
+
+ {card.game === 'Pokemon' && card.type === 'Lightning' && (
+ ⚡
+ )}
+ {card.type}
+
+
+ )}
+ {card.hp && (
+
+ )}
+
+
+ {/* Card Type and Form */}
+
+
+
Card type
+
{card.card_type || 'Card'}
+
+ {card.form && (
+
+ )}
+
+
+ {/* Cost to Play */}
+ {card.mana_cost && (
+
+ )}
+
+ {/* Card Rule Section */}
+ {card.game === 'Pokemon' && card.form && card.form.includes('V') && (
+
+
Card rule
+
+ V rule: When your Pokémon V is Knocked Out, your opponent takes 2 Prize cards.
+
+
+ )}
+
+ {/* Abilities/Attacks/Moves Section */}
+ {card.oracle_text && (
+
+
Card Text
+
{card.oracle_text}
+
+ )}
+
+ {/* Quote Section */}
+ {card.flavor_text && (
+
+
Quote
+
"{card.flavor_text}"
+
+ )}
+
+ {/* Current Price */}
+ {card.current_price && (
+
+
Current Price
+
{formatCurrency(card.current_price)}
+
+ )}
+
+ {/* Bottom Section - Weaknesses and Retreat */}
+
+ {card.weakness && (
+
+
Weaknesses
+
+ {card.game === 'Pokemon' && (
+ 👊
+ )}
+ {card.weakness}
+
+
+ )}
+ {card.retreat_cost && (
+
+
Retreat
+
+ {card.game === 'Pokemon' && (
+ <>
+ ⭐
+ ⭐
+ >
+ )}
+ {card.retreat_cost}
+
+
+ )}
+
+
+ {/* Decks and Collections */}
+ {(card.decks && card.decks.length > 0) || (card.collections && card.collections.length > 0) && (
+
+
Included In
+
+ {card.decks && card.decks.length > 0 && (
+
+ Decks: {card.decks.join(', ')}
+
+ )}
+ {card.collections && card.collections.length > 0 && (
+
+ {VOCAB.LISTS}: {card.collections.join(', ')}
+
+ )}
+
+
+ )}
+
+
+ )}
+
+ );
+}
+
+// Card Back Component for placeholders
+function CardBack({ game }) {
+ const getCardBackImage = () => {
+ switch (game) {
+ case 'MTG':
+ return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjOEI0NTEzIi8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiNBMDUyMkQiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiM0QTkwRTIiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk1BR0lDPC90ZXh0Pgo8dGV4dCB4PSIxMDAiIHk9IjQ1IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5UaGUgR2F0aGVyaW5nPC90ZXh0Pgo8Y2lyY2xlIGN4PSI3MCIgY3k9IjcwIiByPSI0IiBmaWxsPSJ3aGl0ZSIvPgo8Y2lyY2xlIGN4PSIxMzAiIGN4PSI3MCIgcj0iNCIgZmlsbD0iI0Y1OTlFMEIiLz4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iODAiIHI9IjQiIGZpbGw9IiM0QTkwRTIiLz4KPGNpcmNsZSBjeD0iNzAiIGN5PSI5MCIgcj0iNCIgZmlsbD0iIzEwQjk4MSIvPgo8Y2lyY2xlIGN4PSIxMzAiIGN5PSI5MCIgcj0iNCIgZmlsbD0iYmxhY2siLz4KPHRleHQgeD0iMTAwIiB5PSIxMjAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiMyMEIyQUEiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkRFQ0tNQVNURVI8L3RleHQ+Cjwvc3ZnPgo=';
+ case 'Pokemon':
+ return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjRkY2QjZCIi8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiNGRjhFOEUiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiNGRkQ3MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlBPS0VNT048L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iNDUiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlRyYWRpbmcgQ2FyZCBHYW1lPC90ZXh0Pgo8Y2lyY2xlIGN4PSIxMDAiIGN5PSI3MCIgcj0iMjAiIGZpbGw9IndoaXRlIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNzgiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZmlsbD0iIzFGN0Y3RiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+4pePPC90ZXh0Pgo8dGV4dCB4PSIxMDAiIHk9IjEyMCIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEwIiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0iI0ZGRDcwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+R0FNRSBGUkVBSzwvdGV4dD4KPC9zdmc+Cg==';
+ case 'Lorcana':
+ return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjRUM0ODk5Ii8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiNGNDcyQjYiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiNGQkJGMjQiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkRJU05FWTwvdGV4dD4KPHRleHQgeD0iMTAwIiB5PSI0NSIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+TG9yY2FuYTwvdGV4dD4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iNzAiIHI9IjIwIiBmaWxsPSJ3aGl0ZSIvPgo8dGV4dCB4PSIxMDAiIHk9Ijc4IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjQiIGZpbGw9IiMxRjdGN0YiIHRleHQtYW5jaG9yPSJtaWRkbGUiPvCfkqQ8L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iMTIwIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTAiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSIjRjU5RTBCIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5SQVZFTlNERVJHPzwvdGV4dD4KPC9zdmc+Cg==';
+ default:
+ return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjNkI3MjgwIi8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiM5Q0EzQUYiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5UQ0c8L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iNDUiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlRyYWRpbmcgQ2FyZDwvdGV4dD4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iNzAiIHI9IjIwIiBmaWxsPSJ3aGl0ZSIvPgo8dGV4dCB4PSIxMDAiIHk9Ijc4IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjQiIGZpbGw9IiMxRjdGN0YiIHRleHQtYW5jaG9yPSJtaWRkbGUiPvCfkqQ8L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iMTIwIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTAiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSIjNkI3MjgwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5DQVJEPC90ZXh0Pgo8L3N2Zz4K';
+ }
+ };
+
+ return (
+
+
})
+
+ );
+}
diff --git a/pages/cards.js b/pages/cards.js
index ee5e878..57aa942 100644
--- a/pages/cards.js
+++ b/pages/cards.js
@@ -1,4 +1,3 @@
-/* eslint-disable @next/next/no-img-element -- External or generated image URLs; next/image migration is out of scope. */
import { useState, useEffect, useRef } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
@@ -973,533 +972,3 @@ export default function Cards() {
);
}
-
-// 3D Card Component
-function Card3D({ card, viewMode, getRarityLabel, getRarityColor }) {
- const [isHovered, setIsHovered] = useState(false);
- const [cardPosition, setCardPosition] = useState({ x: 0, y: 0, width: 0, height: 0 });
- const [isFavorited, setIsFavorited] = useState(false);
-
- const handleMouseMove = (e) => {
- const rect = e.currentTarget.getBoundingClientRect();
- setCardPosition({
- x: rect.left,
- y: rect.top,
- width: rect.width,
- height: rect.height
- });
- };
-
- const handleMouseLeave = () => {
- setIsHovered(false);
- };
-
- const handleMouseEnter = () => {
- setIsHovered(true);
- };
-
- const getTCGColor = (game) => {
- const colors = {
- 'MTG': '#8B5CF6', // purple
- 'Pokemon': '#3B82F6', // blue
- 'Lorcana': '#EC4899' // pink
- };
- return colors[game] || '#6B7280';
- };
-
- const getSetColor = (setName) => {
- // Generate a consistent color based on set name
- const hash = setName.split('').reduce((a, b) => {
- a = ((a << 5) - a) + b.charCodeAt(0);
- return a & a;
- }, 0);
- const hue = Math.abs(hash) % 360;
- return `hsl(${hue}, 70%, 60%)`;
- };
-
- const formatCurrency = (amount) => {
- return new Intl.NumberFormat('en-US', {
- style: 'currency',
- currency: 'USD'
- }).format(amount);
- };
-
- const getRarityGradient = (rarity) => {
- const gradients = {
- common: 'from-gray-400 to-gray-500',
- uncommon: 'from-green-400 to-emerald-500',
- rare: 'from-yellow-400 to-orange-500',
- mythic: 'from-yellow-400 to-orange-500',
- holographic: 'from-red-400 to-pink-500',
- enchanted: 'from-purple-400 to-indigo-500',
- ultra: 'from-blue-400 to-cyan-500'
- };
- return gradients[rarity] || 'from-gray-400 to-gray-500';
- };
-
- const getRarityGlow = (rarity) => {
- const glows = {
- common: '0 0 15px rgba(156, 163, 175, 0.4)',
- uncommon: '0 0 15px rgba(34, 197, 94, 0.4)',
- rare: '0 0 20px rgba(251, 191, 36, 0.5)',
- mythic: '0 0 20px rgba(255, 215, 0, 0.4), 0 0 40px rgba(255, 215, 0, 0.2), 0 0 60px rgba(255, 215, 0, 0.1)',
- holographic: '0 0 25px rgba(239, 68, 68, 0.6)',
- enchanted: '0 0 20px rgba(168, 85, 247, 0.4), 0 0 40px rgba(168, 85, 247, 0.2), 0 0 60px rgba(168, 85, 247, 0.1)',
- ultra: '0 0 25px rgba(59, 130, 246, 0.6)'
- };
- return glows[rarity] || '0 0 15px rgba(156, 163, 175, 0.4)';
- };
-
- if (viewMode === 'list') {
- return (
-
-
-
-
-
- {card.name}
-
-
- {card.set_name} • {getRarityLabel(card.rarity)}
-
-
- {card.oracle_text || card.card_type}
-
-
-
-
- {formatCurrency(card.current_price || 0)}
-
-
- {getRarityLabel(card.rarity)}
-
-
-
-
- );
- }
-
- return (
-
- {/* Card Container with proper ratio and rarity glow */}
-
- {/* Particle Effects for All Rarities */}
- {card.rarity !== 'common' && (
-
- {/* Get particle count and colors based on rarity */}
- {(() => {
- const rarityConfig = {
- 'mythic': {
- particleCount: 16,
- sparkleCount: 10,
- particleColor: '#FFD700',
- sparkleColor: '#FFA500',
- glowColor: '#FFD700'
- },
- 'enchanted': {
- particleCount: 14,
- sparkleCount: 8,
- particleColor: '#A855F7',
- sparkleColor: '#EC4899',
- glowColor: '#A855F7'
- },
- 'rare': {
- particleCount: 10,
- sparkleCount: 6,
- particleColor: '#3B82F6',
- sparkleColor: '#60A5FA',
- glowColor: '#3B82F6'
- },
- 'uncommon': {
- particleCount: 6,
- sparkleCount: 4,
- particleColor: '#10B981',
- sparkleColor: '#34D399',
- glowColor: '#10B981'
- }
- };
-
- const config = rarityConfig[card.rarity];
- if (!config) return null;
-
- return (
- <>
- {/* Floating Particles - Edge framing */}
-
- {[...Array(config.particleCount)].map((_, i) => {
- // Position particles around the card edges
- let left, top;
- const edgeIndex = i % 4; // 4 edges
- const positionOnEdge = Math.floor(i / 4);
-
- if (edgeIndex === 0) {
- // Top edge
- left = `${10 + (positionOnEdge * 20)}%`;
- top = '2%';
- } else if (edgeIndex === 1) {
- // Right edge
- left = '98%';
- top = `${10 + (positionOnEdge * 20)}%`;
- } else if (edgeIndex === 2) {
- // Bottom edge
- left = `${10 + (positionOnEdge * 20)}%`;
- top = '98%';
- } else {
- // Left edge
- left = '2%';
- top = `${10 + (positionOnEdge * 20)}%`;
- }
-
- return (
-
- );
- })}
-
-
- {/* Sparkle Effects - Corner highlights */}
-
- {[...Array(config.sparkleCount)].map((_, i) => {
- // Position sparkles in the corners and edge centers
- let left, top;
- if (i < 2) {
- // Top corners
- left = i === 0 ? '5%' : '95%';
- top = '5%';
- } else if (i < 4) {
- // Bottom corners
- left = i === 2 ? '5%' : '95%';
- top = '95%';
- } else if (i < 6) {
- // Edge centers
- left = i === 4 ? '50%' : '50%';
- top = i === 4 ? '5%' : '95%';
- } else {
- // Side centers
- left = i === 6 ? '5%' : '95%';
- top = '50%';
- }
-
- return (
-
- );
- })}
-
-
- {/* Rarity Aura - Blend with overall glow */}
-
- >
- );
- })()}
-
- )}
- {/* Card Image */}
-
- {card.image_url ? (
-

{
- e.target.style.display = 'none';
- e.target.nextSibling.style.display = 'flex';
- }}
- />
- ) : null}
-
- {/* Card Back placeholder when no image */}
-
-
-
-
-
- {/* Owned Quantity Chip - Only show if owned */}
- {card.quantity > 0 && (
-
- )}
-
- {/* Favorite Button */}
-
-
-
-
-
- {/* Hover Details Panel */}
- {isHovered && (
-
-
- {/* Card Title */}
-
-
- {/* Top Section - TCG, Set, Type, HP in 2x2 grid */}
-
-
-
-
Set
-
{card.set_name}
-
- {card.type && (
-
-
Type
-
- {card.game === 'Pokemon' && card.type === 'Lightning' && (
- ⚡
- )}
- {card.type}
-
-
- )}
- {card.hp && (
-
- )}
-
-
- {/* Card Type and Form */}
-
-
-
Card type
-
{card.card_type || 'Card'}
-
- {card.form && (
-
- )}
-
-
- {/* Cost to Play */}
- {card.mana_cost && (
-
- )}
-
- {/* Card Rule Section */}
- {card.game === 'Pokemon' && card.form && card.form.includes('V') && (
-
-
Card rule
-
- V rule: When your Pokémon V is Knocked Out, your opponent takes 2 Prize cards.
-
-
- )}
-
- {/* Abilities/Attacks/Moves Section */}
- {card.oracle_text && (
-
-
Card Text
-
{card.oracle_text}
-
- )}
-
- {/* Quote Section */}
- {card.flavor_text && (
-
-
Quote
-
"{card.flavor_text}"
-
- )}
-
- {/* Current Price */}
- {card.current_price && (
-
-
Current Price
-
{formatCurrency(card.current_price)}
-
- )}
-
- {/* Bottom Section - Weaknesses and Retreat */}
-
- {card.weakness && (
-
-
Weaknesses
-
- {card.game === 'Pokemon' && (
- 👊
- )}
- {card.weakness}
-
-
- )}
- {card.retreat_cost && (
-
-
Retreat
-
- {card.game === 'Pokemon' && (
- <>
- ⭐
- ⭐
- >
- )}
- {card.retreat_cost}
-
-
- )}
-
-
- {/* Decks and Collections */}
- {(card.decks && card.decks.length > 0) || (card.collections && card.collections.length > 0) && (
-
-
Included In
-
- {card.decks && card.decks.length > 0 && (
-
- Decks: {card.decks.join(', ')}
-
- )}
- {card.collections && card.collections.length > 0 && (
-
- {VOCAB.LISTS}: {card.collections.join(', ')}
-
- )}
-
-
- )}
-
-
- )}
-
- );
-}
-
-// Card Back Component for placeholders
-function CardBack({ game }) {
- const getCardBackImage = () => {
- switch (game) {
- case 'MTG':
- return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjOEI0NTEzIi8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiNBMDUyMkQiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiM0QTkwRTIiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk1BR0lDPC90ZXh0Pgo8dGV4dCB4PSIxMDAiIHk9IjQ1IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5UaGUgR2F0aGVyaW5nPC90ZXh0Pgo8Y2lyY2xlIGN4PSI3MCIgY3k9IjcwIiByPSI0IiBmaWxsPSJ3aGl0ZSIvPgo8Y2lyY2xlIGN4PSIxMzAiIGN4PSI3MCIgcj0iNCIgZmlsbD0iI0Y1OTlFMEIiLz4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iODAiIHI9IjQiIGZpbGw9IiM0QTkwRTIiLz4KPGNpcmNsZSBjeD0iNzAiIGN5PSI5MCIgcj0iNCIgZmlsbD0iIzEwQjk4MSIvPgo8Y2lyY2xlIGN4PSIxMzAiIGN5PSI5MCIgcj0iNCIgZmlsbD0iYmxhY2siLz4KPHRleHQgeD0iMTAwIiB5PSIxMjAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiMyMEIyQUEiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkRFQ0tNQVNURVI8L3RleHQ+Cjwvc3ZnPgo=';
- case 'Pokemon':
- return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjRkY2QjZCIi8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiNGRjhFOEUiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiNGRkQ3MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlBPS0VNT048L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iNDUiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlRyYWRpbmcgQ2FyZCBHYW1lPC90ZXh0Pgo8Y2lyY2xlIGN4PSIxMDAiIGN5PSI3MCIgcj0iMjAiIGZpbGw9IndoaXRlIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNzgiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZmlsbD0iIzFGN0Y3RiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+4pePPC90ZXh0Pgo8dGV4dCB4PSIxMDAiIHk9IjEyMCIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEwIiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0iI0ZGRDcwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+R0FNRSBGUkVBSzwvdGV4dD4KPC9zdmc+Cg==';
- case 'Lorcana':
- return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjRUM0ODk5Ii8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiNGNDcyQjYiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiNGQkJGMjQiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkRJU05FWTwvdGV4dD4KPHRleHQgeD0iMTAwIiB5PSI0NSIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+TG9yY2FuYTwvdGV4dD4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iNzAiIHI9IjIwIiBmaWxsPSJ3aGl0ZSIvPgo8dGV4dCB4PSIxMDAiIHk9Ijc4IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjQiIGZpbGw9IiMxRjdGN0YiIHRleHQtYW5jaG9yPSJtaWRkbGUiPvCfkqQ8L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iMTIwIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTAiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSIjRjU5RTBCIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5SQVZFTlNERVJHPzwvdGV4dD4KPC9zdmc+Cg==';
- default:
- return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjNkI3MjgwIi8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiM5Q0EzQUYiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5UQ0c8L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iNDUiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlRyYWRpbmcgQ2FyZDwvdGV4dD4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iNzAiIHI9IjIwIiBmaWxsPSJ3aGl0ZSIvPgo8dGV4dCB4PSIxMDAiIHk9Ijc4IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjQiIGZpbGw9IiMxRjdGN0YiIHRleHQtYW5jaG9yPSJtaWRkbGUiPvCfkqQ8L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iMTIwIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTAiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSIjNkI3MjgwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5DQVJEPC90ZXh0Pgo8L3N2Zz4K';
- }
- };
-
- return (
-
-
})
-
- );
-}
\ No newline at end of file