feat: Add numerous Magic: The Gathering card metadata and image files.
Some checks failed
Build and Deploy / build (push) Failing after 50s
Some checks failed
Build and Deploy / build (push) Failing after 50s
This commit is contained in:
@@ -147,6 +147,13 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
const [deck, setDeck] = useState<any[]>([]);
|
const [deck, setDeck] = useState<any[]>([]);
|
||||||
const [lands, setLands] = useState({ Plains: 0, Island: 0, Swamp: 0, Mountain: 0, Forest: 0 });
|
const [lands, setLands] = useState({ Plains: 0, Island: 0, Swamp: 0, Mountain: 0, Forest: 0 });
|
||||||
const [hoveredCard, setHoveredCard] = useState<any>(null);
|
const [hoveredCard, setHoveredCard] = useState<any>(null);
|
||||||
|
const [displayCard, setDisplayCard] = useState<any>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (hoveredCard) {
|
||||||
|
setDisplayCard(hoveredCard);
|
||||||
|
}
|
||||||
|
}, [hoveredCard]);
|
||||||
|
|
||||||
// --- Land Advice Logic ---
|
// --- Land Advice Logic ---
|
||||||
const landSuggestion = useMemo(() => {
|
const landSuggestion = useMemo(() => {
|
||||||
@@ -381,29 +388,56 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
|
|
||||||
<div className="flex-1 flex overflow-hidden">
|
<div className="flex-1 flex overflow-hidden">
|
||||||
{/* Zoom Sidebar */}
|
{/* Zoom Sidebar */}
|
||||||
<div className="hidden xl:flex w-72 shrink-0 flex-col items-center justify-start pt-4 border-r border-slate-800 bg-slate-900 z-10 p-4">
|
<div className="hidden xl:flex w-72 shrink-0 flex-col items-center justify-start pt-4 border-r border-slate-800 bg-slate-900 z-10 p-4" style={{ perspective: '1000px' }}>
|
||||||
{hoveredCard ? (
|
<div className="w-full relative sticky top-4">
|
||||||
<div key={hoveredCard.id} className="animate-in fade-in duration-300 sticky top-4 w-full">
|
<div
|
||||||
<img
|
className="relative w-full aspect-[2.5/3.5] transition-all duration-300 ease-in-out"
|
||||||
src={hoveredCard.image || hoveredCard.image_uris?.normal || hoveredCard.card_faces?.[0]?.image_uris?.normal}
|
style={{
|
||||||
alt={hoveredCard.name}
|
transformStyle: 'preserve-3d',
|
||||||
className="w-full rounded-xl shadow-2xl shadow-black ring-1 ring-white/10"
|
transform: hoveredCard ? 'rotateY(0deg)' : 'rotateY(180deg)'
|
||||||
/>
|
}}
|
||||||
<div className="mt-4 text-center">
|
>
|
||||||
<h3 className="text-lg font-bold text-slate-200">{hoveredCard.name}</h3>
|
{/* Front Face (Hovered Card) */}
|
||||||
<p className="text-xs text-slate-400 uppercase tracking-wider mt-1">{hoveredCard.type_line}</p>
|
<div
|
||||||
{hoveredCard.oracle_text && (
|
className="absolute inset-0 w-full h-full bg-slate-900 rounded-xl"
|
||||||
<div className="mt-4 text-xs text-slate-400 text-left bg-slate-950 p-3 rounded-lg border border-slate-800 leading-relaxed">
|
style={{ backfaceVisibility: 'hidden' }}
|
||||||
{hoveredCard.oracle_text.split('\n').map((line: string, i: number) => <p key={i} className="mb-1">{line}</p>)}
|
>
|
||||||
|
{(hoveredCard || displayCard) && (
|
||||||
|
<div className="w-full h-full flex flex-col bg-slate-900 rounded-xl">
|
||||||
|
<img
|
||||||
|
src={(hoveredCard || displayCard).image || (hoveredCard || displayCard).image_uris?.normal || (hoveredCard || displayCard).card_faces?.[0]?.image_uris?.normal}
|
||||||
|
alt={(hoveredCard || displayCard).name}
|
||||||
|
className="w-full rounded-xl shadow-2xl shadow-black ring-1 ring-white/10"
|
||||||
|
/>
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<h3 className="text-lg font-bold text-slate-200">{(hoveredCard || displayCard).name}</h3>
|
||||||
|
<p className="text-xs text-slate-400 uppercase tracking-wider mt-1">{(hoveredCard || displayCard).type_line}</p>
|
||||||
|
{(hoveredCard || displayCard).oracle_text && (
|
||||||
|
<div className="mt-4 text-xs text-slate-400 text-left bg-slate-950 p-3 rounded-lg border border-slate-800 leading-relaxed">
|
||||||
|
{(hoveredCard || displayCard).oracle_text.split('\n').map((line: string, i: number) => <p key={i} className="mb-1">{line}</p>)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Back Face (Card Back) */}
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 w-full h-full rounded-xl shadow-2xl overflow-hidden bg-slate-900"
|
||||||
|
style={{
|
||||||
|
backfaceVisibility: 'hidden',
|
||||||
|
transform: 'rotateY(180deg)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/back.jpg"
|
||||||
|
alt="Card Back"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<div className="flex flex-col items-center justify-center h-64 text-slate-600 text-center opacity-50 border-2 border-dashed border-slate-800 rounded-xl mt-10">
|
|
||||||
<span className="text-xs uppercase font-bold tracking-widest">Hover Card</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Area */}
|
{/* Content Area */}
|
||||||
@@ -414,7 +448,7 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
<div className="p-3 border-b border-slate-800 font-bold text-slate-400 uppercase text-xs flex justify-between">
|
<div className="p-3 border-b border-slate-800 font-bold text-slate-400 uppercase text-xs flex justify-between">
|
||||||
<span>Card Pool ({pool.length})</span>
|
<span>Card Pool ({pool.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-2 custom-scrollbar flex flex-col">
|
<div className="flex-1 overflow-auto p-2 custom-scrollbar flex flex-col">
|
||||||
{renderLandStation()}
|
{renderLandStation()}
|
||||||
<CardsDisplay cards={pool} viewMode={viewMode} cardWidth={cardWidth} onCardClick={addToDeck} onHover={setHoveredCard} emptyMessage="Pool Empty" />
|
<CardsDisplay cards={pool} viewMode={viewMode} cardWidth={cardWidth} onCardClick={addToDeck} onHover={setHoveredCard} emptyMessage="Pool Empty" />
|
||||||
</div>
|
</div>
|
||||||
@@ -424,7 +458,7 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
<div className="p-3 border-b border-slate-800 font-bold text-slate-400 uppercase text-xs flex justify-between">
|
<div className="p-3 border-b border-slate-800 font-bold text-slate-400 uppercase text-xs flex justify-between">
|
||||||
<span>Deck ({deck.length})</span>
|
<span>Deck ({deck.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-2 custom-scrollbar">
|
<div className="flex-1 overflow-auto p-2 custom-scrollbar">
|
||||||
<CardsDisplay cards={deck} viewMode={viewMode} cardWidth={cardWidth} onCardClick={removeFromDeck} onHover={setHoveredCard} emptyMessage="Your Deck is Empty" />
|
<CardsDisplay cards={deck} viewMode={viewMode} cardWidth={cardWidth} onCardClick={removeFromDeck} onHover={setHoveredCard} emptyMessage="Your Deck is Empty" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -436,7 +470,7 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
<div className="p-2 border-b border-slate-800 font-bold text-slate-400 uppercase text-xs flex justify-between shrink-0">
|
<div className="p-2 border-b border-slate-800 font-bold text-slate-400 uppercase text-xs flex justify-between shrink-0">
|
||||||
<span>Card Pool ({pool.length})</span>
|
<span>Card Pool ({pool.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-2 custom-scrollbar flex flex-col">
|
<div className="flex-1 overflow-auto p-2 custom-scrollbar flex flex-col">
|
||||||
{renderLandStation()}
|
{renderLandStation()}
|
||||||
<CardsDisplay cards={pool} viewMode={viewMode} cardWidth={cardWidth} onCardClick={addToDeck} onHover={setHoveredCard} emptyMessage="Pool Empty" />
|
<CardsDisplay cards={pool} viewMode={viewMode} cardWidth={cardWidth} onCardClick={addToDeck} onHover={setHoveredCard} emptyMessage="Pool Empty" />
|
||||||
</div>
|
</div>
|
||||||
@@ -446,7 +480,7 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool, a
|
|||||||
<div className="p-2 border-b border-slate-800 font-bold text-slate-400 uppercase text-xs flex justify-between shrink-0">
|
<div className="p-2 border-b border-slate-800 font-bold text-slate-400 uppercase text-xs flex justify-between shrink-0">
|
||||||
<span>Deck ({deck.length})</span>
|
<span>Deck ({deck.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-2 custom-scrollbar">
|
<div className="flex-1 overflow-auto p-2 custom-scrollbar">
|
||||||
<CardsDisplay cards={deck} viewMode={viewMode} cardWidth={cardWidth} onCardClick={removeFromDeck} onHover={setHoveredCard} emptyMessage="Your Deck is Empty" />
|
<CardsDisplay cards={deck} viewMode={viewMode} cardWidth={cardWidth} onCardClick={removeFromDeck} onHover={setHoveredCard} emptyMessage="Your Deck is Empty" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ app.use(express.json({ limit: '50mb' })); // Increase limit for large card lists
|
|||||||
|
|
||||||
// Serve static images (Nested)
|
// Serve static images (Nested)
|
||||||
app.use('/cards', express.static(path.join(__dirname, 'public/cards')));
|
app.use('/cards', express.static(path.join(__dirname, 'public/cards')));
|
||||||
|
app.use('/images', express.static(path.join(__dirname, 'public/images')));
|
||||||
|
|
||||||
// API Routes
|
// API Routes
|
||||||
app.get('/api/health', (_req: Request, res: Response) => {
|
app.get('/api/health', (_req: Request, res: Response) => {
|
||||||
|
|||||||
BIN
src/server/public/images/back.jpg
Normal file
BIN
src/server/public/images/back.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 510 KiB |
@@ -19,6 +19,7 @@ export default defineConfig({
|
|||||||
proxy: {
|
proxy: {
|
||||||
'/api': 'http://localhost:3000', // Proxy API requests to backend
|
'/api': 'http://localhost:3000', // Proxy API requests to backend
|
||||||
'/cards': 'http://localhost:3000', // Proxy cached card images
|
'/cards': 'http://localhost:3000', // Proxy cached card images
|
||||||
|
'/images': 'http://localhost:3000', // Proxy static images
|
||||||
'/socket.io': {
|
'/socket.io': {
|
||||||
target: 'http://localhost:3000',
|
target: 'http://localhost:3000',
|
||||||
ws: true
|
ws: true
|
||||||
|
|||||||
Reference in New Issue
Block a user