refactor(deck-builder): extract deck list and card browser (Brief 2) #91

Merged
varutasu merged 1 commit from refactor/deck-builder-split-brief-2 into main 2026-06-03 18:29:08 -04:00
3 changed files with 882 additions and 444 deletions
Showing only changes of commit 9f620b119f - Show all commits

View file

@ -0,0 +1,761 @@
/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
import { ManaCost, ColorFilterSymbol } from './ManaSymbols';
import ManaSymbolSettings from './ManaSymbolSettings';
export default function DeckBuilderCardBrowser({
selectedCard,
onSelectCard,
onClearSelectedCard,
viewMode,
onViewModeChange,
showSettings,
onToggleSettings,
onCollapseSidebar,
searchQuery,
onSearchQueryChange,
showFilters,
onToggleFilters,
filters,
onFiltersChange,
onClearFilters,
onToggleColorFilter,
searchLoading,
searchResults,
manaSymbolSettings,
onManaSymbolSettingsChange,
onAddCardToDeck,
}) {
return (
<div className="bg-bg-tertiary rounded-lg h-full flex flex-col relative border border-border">
{!selectedCard ? (
<>
{/* Header */}
<div className="flex justify-between items-center p-4 border-b border-border bg-bg-secondary rounded-t-lg">
<h3 className="text-lg font-semibold text-text-primary">Card Browser</h3>
<div className="flex items-center space-x-2">
{/* View Mode Toggle */}
<div className="flex bg-bg-primary rounded-lg p-1">
<button
onClick={() => onViewModeChange('list')}
className={`px-2 py-1 rounded text-xs transition-colors ${
viewMode === 'list'
? 'bg-accent-ember text-white'
: 'text-text-secondary hover:text-text-primary'
}`}
title="List View"
>
</button>
<button
onClick={() => onViewModeChange('thumbnail')}
className={`px-2 py-1 rounded text-xs transition-colors ${
viewMode === 'thumbnail'
? 'bg-accent-ember text-white'
: 'text-text-secondary hover:text-text-primary'
}`}
title="Thumbnail View"
>
</button>
</div>
<button
onClick={() => onToggleSettings()}
className={`text-text-secondary hover:text-text-primary transition-colors p-1 ${
showSettings ? 'text-accent-ember' : ''
}`}
title="Settings"
>
</button>
<button
onClick={() => onCollapseSidebar()}
className="text-text-secondary hover:text-text-primary transition-colors p-1"
title="Collapse"
>
</button>
</div>
</div>
{/* Search */}
<div className="p-4 border-b border-border">
<div className="flex space-x-2">
<input
type="text"
value={searchQuery}
onChange={(e) => onSearchQueryChange(e.target.value)}
placeholder="Search for cards..."
className="flex-1 px-3 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-ember bg-bg-primary text-text-primary text-sm"
/>
<button
onClick={() => onToggleFilters()}
className={`p-2 rounded-lg transition-colors ${
showFilters || filters.colors.length > 0 || filters.cmc || filters.rarity
? 'bg-accent-ember text-white'
: 'bg-bg-primary text-text-secondary hover:bg-bg-tertiary'
}`}
title="Filters"
>
🔍
</button>
</div>
</div>
{/* Settings Panel */}
{showSettings && (
<div className="p-4 border-b border-border bg-bg-primary">
<ManaSymbolSettings onSettingsChange={onManaSymbolSettingsChange} />
</div>
)}
{/* Quick Filters */}
{showFilters && (
<div className="p-4 border-b border-border bg-bg-primary space-y-3">
{/* Color Filters */}
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-text-secondary text-xs font-medium">Colors</label>
{(filters.colors.length > 0 || filters.cmc || filters.rarity) && (
<button
onClick={onClearFilters}
className="text-xs text-accent-ember hover:underline"
>
Clear All
</button>
)}
</div>
<div className="flex space-x-1">
{['W', 'U', 'B', 'R', 'G'].map(color => (
<ColorFilterSymbol
key={color}
color={color}
isActive={filters.colors.includes(color)}
onClick={onToggleColorFilter}
size="sm"
useSVG={manaSymbolSettings.useSVG}
/>
))}
</div>
</div>
{/* CMC and Rarity */}
<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">CMC</label>
<select
value={filters.cmc}
onChange={(e) => onFiltersChange({ ...filters, cmc: e.target.value })}
className="w-full px-2 py-1 border border-border rounded text-xs focus:outline-none focus:ring-1 focus:ring-accent-ember bg-bg-secondary text-text-primary"
>
<option value="">Any</option>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6+">6+</option>
</select>
</div>
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Rarity</label>
<select
value={filters.rarity}
onChange={(e) => onFiltersChange({ ...filters, rarity: e.target.value })}
className="w-full px-2 py-1 border border-border rounded text-xs focus:outline-none focus:ring-1 focus:ring-accent-ember bg-bg-secondary text-text-primary"
>
<option value="">Any</option>
<option value="common">Common</option>
<option value="uncommon">Uncommon</option>
<option value="rare">Rare</option>
<option value="mythic">Mythic</option>
</select>
</div>
</div>
</div>
)}
{/* Card List */}
<div className="flex-1 overflow-y-auto p-4">
{viewMode === 'list' ? (
/* List View */
<div className="space-y-1">
{searchLoading ? (
<div className="text-center py-8">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-accent-ember mx-auto"></div>
<p className="text-text-secondary mt-2 text-xs">Searching...</p>
</div>
) : searchResults.length === 0 ? (
<div className="text-center py-8">
<div className="text-2xl mb-2">🔍</div>
<p className="text-text-secondary text-xs">
{searchQuery ? 'No cards found' : 'No cards available'}
</p>
</div>
) : (
searchResults.map((card) => (
<div
key={card.id}
className="flex items-center p-2 bg-bg-secondary rounded hover:bg-bg-primary transition-colors cursor-pointer"
onClick={() => onSelectCard(card)}
>
<div className="flex items-center space-x-2 flex-1 min-w-0">
{card.image_url && (
<img
src={card.image_url}
alt={card.name}
className="w-8 h-11 object-cover rounded flex-shrink-0"
/>
)}
<div className="min-w-0 flex-1">
<h4 className="font-medium text-text-primary text-xs truncate">{card.name}</h4>
<p className="text-text-secondary text-xs truncate">{card.set_name}</p>
<div className="flex items-center space-x-1">
{card.mana_cost && (
<ManaCost cost={card.mana_cost} size="xs" useSVG={manaSymbolSettings.useSVG} />
)}
{card.rarity && (
<span className={`text-xs px-1 rounded capitalize ${
card.rarity === 'mythic' ? 'bg-orange-100 text-orange-800' :
card.rarity === 'rare' ? 'bg-yellow-100 text-yellow-800' :
card.rarity === 'uncommon' ? 'bg-gray-100 text-gray-800' :
'bg-green-100 text-green-800'
}`}>
{card.rarity[0].toUpperCase()}
</span>
)}
</div>
</div>
</div>
</div>
))
)}
</div>
) : (
/* Thumbnail View */
<div>
{searchLoading ? (
<div className="text-center py-8">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-accent-ember mx-auto"></div>
<p className="text-text-secondary mt-2 text-xs">Searching...</p>
</div>
) : searchResults.length === 0 ? (
<div className="text-center py-8">
<div className="text-2xl mb-2">🔍</div>
<p className="text-text-secondary text-xs">
{searchQuery ? 'No cards found' : 'No cards available'}
</p>
</div>
) : (
<div className="grid grid-cols-3 gap-2">
{searchResults.map((card) => (
<div
key={card.id}
className="relative group cursor-pointer"
onClick={() => onSelectCard(card)}
>
{card.image_url ? (
<div className="relative">
<img
src={card.image_url}
alt={card.name}
className="w-full aspect-[2.5/3.5] object-cover rounded-lg shadow-sm group-hover:shadow-md transition-shadow"
/>
{/* Hover overlay with card name */}
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-60 transition-all duration-200 rounded-lg flex items-end">
<div className="p-2 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<p className="text-xs font-medium truncate">{card.name}</p>
<p className="text-xs opacity-75 truncate">{card.set_name}</p>
</div>
</div>
{/* Rarity indicator */}
{card.rarity && (
<div className={`absolute top-1 right-1 w-2 h-2 rounded-full ${
card.rarity === 'mythic' ? 'bg-orange-500' :
card.rarity === 'rare' ? 'bg-yellow-500' :
card.rarity === 'uncommon' ? 'bg-gray-400' :
'bg-green-500'
}`}></div>
)}
</div>
) : (
<div className="w-full aspect-[2.5/3.5] bg-bg-secondary rounded-lg flex items-center justify-center group-hover:bg-bg-primary transition-colors">
<div className="text-center p-2">
<p className="text-xs font-medium text-text-primary truncate">{card.name}</p>
<p className="text-xs text-text-secondary truncate">{card.set_name}</p>
</div>
</div>
)}
</div>
))}
</div>
)}
</div>
)}
</div>
</>
) : (
/* Card Detail View */
<>
{/* Detail Header */}
<div className="flex items-center p-4 border-b border-border bg-bg-secondary rounded-t-lg">
<button
onClick={() => onClearSelectedCard()}
className="text-text-secondary hover:text-text-primary transition-colors mr-3"
>
Back
</button>
<h3 className="text-lg font-semibold text-text-primary truncate">{selectedCard.name}</h3>
</div>
{/* Card Detail Content */}
<div className="flex-1 overflow-y-auto p-4">
<div className="space-y-4">
{/* Card Image */}
{selectedCard.image_url && (
<div className="text-center">
<img
src={selectedCard.image_url}
alt={selectedCard.name}
className="w-full max-w-64 mx-auto rounded-lg shadow-lg"
/>
</div>
)}
{/* Card Info */}
<div className="space-y-3">
<div>
<h4 className="font-semibold text-text-primary text-lg">{selectedCard.name}</h4>
<p className="text-text-secondary text-sm">{selectedCard.set_name}</p>
</div>
{selectedCard.mana_cost && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Mana Cost</label>
<div className="bg-bg-primary px-3 py-2 rounded">
<ManaCost cost={selectedCard.mana_cost} size="md" useSVG={manaSymbolSettings.useSVG} />
</div>
</div>
)}
{selectedCard.card_type && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Type</label>
<div className="bg-bg-primary px-3 py-2 rounded text-sm">{selectedCard.card_type}</div>
</div>
)}
{selectedCard.oracle_text && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Oracle Text</label>
<div className="bg-bg-primary px-3 py-2 rounded text-sm whitespace-pre-wrap">{selectedCard.oracle_text}</div>
</div>
)}
<div className="grid grid-cols-2 gap-4">
{selectedCard.rarity && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Rarity</label>
<div className={`px-3 py-2 rounded text-sm capitalize ${
selectedCard.rarity === 'mythic' ? 'bg-orange-100 text-orange-800' :
selectedCard.rarity === 'rare' ? 'bg-yellow-100 text-yellow-800' :
selectedCard.rarity === 'uncommon' ? 'bg-gray-100 text-gray-800' :
'bg-green-100 text-green-800'
}`}>
{selectedCard.rarity}
</div>
</div>
)}
{selectedCard.cmc !== undefined && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">CMC</label>
<div className="bg-bg-primary px-3 py-2 rounded text-sm">{selectedCard.cmc}</div>
</div>
)}
</div>
</div>
</div>
</div>
</>
)}
{/* Floating Action Button */}
{selectedCard && (
<div className="absolute bottom-4 left-4 right-4">
<button
onClick={() => onAddCardToDeck(selectedCard)}
className="w-full bg-accent-ember text-white py-3 rounded-lg hover:bg-accent-ember-dark transition-colors font-semibold shadow-lg"
>
Add to Deck
</button>
</div>
)}
</div>
);
}

View file

@ -0,0 +1,85 @@
/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
import { ManaCost } from './ManaSymbols';
export default function DeckBuilderDeckList({
deckCards,
sidebarOpen,
onOpenSidebar,
manaSymbolSettings,
onAddCard,
onRemoveCard,
}) {
return (
<div className="flex-1 overflow-y-auto space-y-2">
{deckCards.length === 0 ? (
<div className="text-center py-12">
<div className="text-6xl mb-4">🃏</div>
<h3 className="text-xl font-semibold text-text-primary mb-2">Empty Deck</h3>
<p className="text-text-secondary mb-4">Start building your deck by searching for cards</p>
{!sidebarOpen && (
<button
onClick={onOpenSidebar}
className="bg-accent-ember text-white px-4 py-2 rounded-lg hover:bg-accent-ember-dark transition-colors"
>
Open Card Browser
</button>
)}
</div>
) : (
deckCards
.sort((a, b) => a.name.localeCompare(b.name))
.map((card) => (
<div key={`${card.card_id}-${card.id}`} className="flex items-center justify-between p-4 bg-bg-primary rounded-lg hover:bg-bg-tertiary transition-colors">
<div className="flex items-center space-x-4">
{card.image_url && (
<img
src={card.image_url}
alt={card.name}
className="w-14 h-20 object-cover rounded shadow-md"
/>
)}
<div>
<h4 className="font-semibold text-text-primary text-lg">{card.name}</h4>
<p className="text-text-secondary text-sm">{card.set_name}</p>
<div className="flex items-center space-x-3 mt-1">
{card.mana_cost && (
<div className="bg-bg-secondary px-2 py-1 rounded">
<ManaCost cost={card.mana_cost} size="sm" useSVG={manaSymbolSettings.useSVG} />
</div>
)}
{card.rarity && (
<span className={`text-xs px-2 py-1 rounded capitalize ${
card.rarity === 'mythic' ? 'bg-orange-100 text-orange-800' :
card.rarity === 'rare' ? 'bg-yellow-100 text-yellow-800' :
card.rarity === 'uncommon' ? 'bg-gray-100 text-gray-800' :
'bg-green-100 text-green-800'
}`}>
{card.rarity}
</span>
)}
</div>
</div>
</div>
<div className="flex items-center space-x-3">
<span className="text-text-primary font-bold text-lg">{card.quantity}x</span>
<div className="flex items-center space-x-1">
<button
onClick={() => onRemoveCard(card.card_id, 1)}
className="w-8 h-8 bg-red-500 text-white rounded-full hover:bg-red-600 transition-colors flex items-center justify-center font-bold"
>
</button>
<button
onClick={() => onAddCard(card, 1)}
className="w-8 h-8 bg-accent-ember text-white rounded-full hover:bg-accent-ember-dark transition-colors flex items-center justify-center font-bold"
>
+
</button>
</div>
</div>
</div>
))
)}
</div>
);
}

View file

@ -3,12 +3,11 @@ import { useState, useEffect, useRef } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Link from 'next/link'; import Link from 'next/link';
import Layout from '../components/Layout'; import Layout from '../components/Layout';
import DeckBuilderCardBrowser from '../components/DeckBuilderCardBrowser';
import DeckBuilderDeckList from '../components/DeckBuilderDeckList';
import DeckBuilderStatsBar from '../components/DeckBuilderStatsBar'; import DeckBuilderStatsBar from '../components/DeckBuilderStatsBar';
import { ManaCost, ColorIdentity, ColorFilterSymbol } from '../components/ManaSymbols';
import ManaSymbolSettings from '../components/ManaSymbolSettings';
import { useAuth } from '../lib/use-auth'; import { useAuth } from '../lib/use-auth';
import { computeDeckStats, isBasicLand } from '../lib/deck-builder-stats'; import { computeDeckStats, isBasicLand } from '../lib/deck-builder-stats';
import { getColorIdentity, getColorSymbol } from '../lib/mana-symbols';
export default function DeckBuilder() { export default function DeckBuilder() {
const { user } = useAuth(); const { user } = useAuth();
@ -317,452 +316,45 @@ export default function DeckBuilder() {
<DeckBuilderStatsBar stats={stats} /> <DeckBuilderStatsBar stats={stats} />
{/* Deck Cards List */} <DeckBuilderDeckList
<div className="flex-1 overflow-y-auto space-y-2"> deckCards={deckCards}
{deckCards.length === 0 ? ( sidebarOpen={sidebarOpen}
<div className="text-center py-12"> onOpenSidebar={() => setSidebarOpen(true)}
<div className="text-6xl mb-4">🃏</div> manaSymbolSettings={manaSymbolSettings}
<h3 className="text-xl font-semibold text-text-primary mb-2">Empty Deck</h3> onAddCard={(card, quantity) => addCardToDeck(card, quantity)}
<p className="text-text-secondary mb-4">Start building your deck by searching for cards</p> onRemoveCard={(cardId, quantity) => removeCardFromDeck(cardId, quantity)}
{!sidebarOpen && (
<button
onClick={() => setSidebarOpen(true)}
className="bg-accent-ember text-white px-4 py-2 rounded-lg hover:bg-accent-ember-dark transition-colors"
>
Open Card Browser
</button>
)}
</div>
) : (
deckCards
.sort((a, b) => a.name.localeCompare(b.name))
.map((card) => (
<div key={`${card.card_id}-${card.id}`} className="flex items-center justify-between p-4 bg-bg-primary rounded-lg hover:bg-bg-tertiary transition-colors">
<div className="flex items-center space-x-4">
{card.image_url && (
<img
src={card.image_url}
alt={card.name}
className="w-14 h-20 object-cover rounded shadow-md"
/> />
)}
<div>
<h4 className="font-semibold text-text-primary text-lg">{card.name}</h4>
<p className="text-text-secondary text-sm">{card.set_name}</p>
<div className="flex items-center space-x-3 mt-1">
{card.mana_cost && (
<div className="bg-bg-secondary px-2 py-1 rounded">
<ManaCost cost={card.mana_cost} size="sm" useSVG={manaSymbolSettings.useSVG} />
</div>
)}
{card.rarity && (
<span className={`text-xs px-2 py-1 rounded capitalize ${
card.rarity === 'mythic' ? 'bg-orange-100 text-orange-800' :
card.rarity === 'rare' ? 'bg-yellow-100 text-yellow-800' :
card.rarity === 'uncommon' ? 'bg-gray-100 text-gray-800' :
'bg-green-100 text-green-800'
}`}>
{card.rarity}
</span>
)}
</div>
</div>
</div>
<div className="flex items-center space-x-3">
<span className="text-text-primary font-bold text-lg">{card.quantity}x</span>
<div className="flex items-center space-x-1">
<button
onClick={() => removeCardFromDeck(card.card_id, 1)}
className="w-8 h-8 bg-red-500 text-white rounded-full hover:bg-red-600 transition-colors flex items-center justify-center font-bold"
>
</button>
<button
onClick={() => addCardToDeck(card, 1)}
className="w-8 h-8 bg-accent-ember text-white rounded-full hover:bg-accent-ember-dark transition-colors flex items-center justify-center font-bold"
>
+
</button>
</div>
</div>
</div>
))
)}
</div>
</div> </div>
</div> </div>
{/* Full Height Right Sidebar - Card Browser */} {/* Full Height Right Sidebar - Card Browser */}
<div className={`transition-all duration-300 ${sidebarOpen ? 'w-96' : 'w-0 overflow-hidden'}`}> <div className={`transition-all duration-300 ${sidebarOpen ? 'w-96' : 'w-0 overflow-hidden'}`}>
<div className="bg-bg-tertiary rounded-lg h-full flex flex-col relative border border-border"> <DeckBuilderCardBrowser
{!selectedCard ? ( selectedCard={selectedCard}
<> onSelectCard={setSelectedCard}
{/* Header */} onClearSelectedCard={() => setSelectedCard(null)}
<div className="flex justify-between items-center p-4 border-b border-border bg-bg-secondary rounded-t-lg"> viewMode={viewMode}
<h3 className="text-lg font-semibold text-text-primary">Card Browser</h3> onViewModeChange={setViewMode}
<div className="flex items-center space-x-2"> showSettings={showSettings}
{/* View Mode Toggle */} onToggleSettings={() => setShowSettings(!showSettings)}
<div className="flex bg-bg-primary rounded-lg p-1"> onCollapseSidebar={() => setSidebarOpen(false)}
<button searchQuery={searchQuery}
onClick={() => setViewMode('list')} onSearchQueryChange={setSearchQuery}
className={`px-2 py-1 rounded text-xs transition-colors ${ showFilters={showFilters}
viewMode === 'list' onToggleFilters={() => setShowFilters(!showFilters)}
? 'bg-accent-ember text-white' filters={filters}
: 'text-text-secondary hover:text-text-primary' onFiltersChange={setFilters}
}`} onClearFilters={clearFilters}
title="List View" onToggleColorFilter={toggleColorFilter}
> searchLoading={searchLoading}
searchResults={searchResults}
</button> manaSymbolSettings={manaSymbolSettings}
<button onManaSymbolSettingsChange={setManaSymbolSettings}
onClick={() => setViewMode('thumbnail')} onAddCardToDeck={(card) => {
className={`px-2 py-1 rounded text-xs transition-colors ${ addCardToDeck(card);
viewMode === 'thumbnail'
? 'bg-accent-ember text-white'
: 'text-text-secondary hover:text-text-primary'
}`}
title="Thumbnail View"
>
</button>
</div>
<button
onClick={() => setShowSettings(!showSettings)}
className={`text-text-secondary hover:text-text-primary transition-colors p-1 ${
showSettings ? 'text-accent-ember' : ''
}`}
title="Settings"
>
</button>
<button
onClick={() => setSidebarOpen(false)}
className="text-text-secondary hover:text-text-primary transition-colors p-1"
title="Collapse"
>
</button>
</div>
</div>
{/* Search */}
<div className="p-4 border-b border-border">
<div className="flex space-x-2">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search for cards..."
className="flex-1 px-3 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-ember bg-bg-primary text-text-primary text-sm"
/>
<button
onClick={() => setShowFilters(!showFilters)}
className={`p-2 rounded-lg transition-colors ${
showFilters || filters.colors.length > 0 || filters.cmc || filters.rarity
? 'bg-accent-ember text-white'
: 'bg-bg-primary text-text-secondary hover:bg-bg-tertiary'
}`}
title="Filters"
>
🔍
</button>
</div>
</div>
{/* Settings Panel */}
{showSettings && (
<div className="p-4 border-b border-border bg-bg-primary">
<ManaSymbolSettings onSettingsChange={setManaSymbolSettings} />
</div>
)}
{/* Quick Filters */}
{showFilters && (
<div className="p-4 border-b border-border bg-bg-primary space-y-3">
{/* Color Filters */}
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-text-secondary text-xs font-medium">Colors</label>
{(filters.colors.length > 0 || filters.cmc || filters.rarity) && (
<button
onClick={clearFilters}
className="text-xs text-accent-ember hover:underline"
>
Clear All
</button>
)}
</div>
<div className="flex space-x-1">
{['W', 'U', 'B', 'R', 'G'].map(color => (
<ColorFilterSymbol
key={color}
color={color}
isActive={filters.colors.includes(color)}
onClick={toggleColorFilter}
size="sm"
useSVG={manaSymbolSettings.useSVG}
/>
))}
</div>
</div>
{/* CMC and Rarity */}
<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">CMC</label>
<select
value={filters.cmc}
onChange={(e) => setFilters({...filters, cmc: e.target.value})}
className="w-full px-2 py-1 border border-border rounded text-xs focus:outline-none focus:ring-1 focus:ring-accent-ember bg-bg-secondary text-text-primary"
>
<option value="">Any</option>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6+">6+</option>
</select>
</div>
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Rarity</label>
<select
value={filters.rarity}
onChange={(e) => setFilters({...filters, rarity: e.target.value})}
className="w-full px-2 py-1 border border-border rounded text-xs focus:outline-none focus:ring-1 focus:ring-accent-ember bg-bg-secondary text-text-primary"
>
<option value="">Any</option>
<option value="common">Common</option>
<option value="uncommon">Uncommon</option>
<option value="rare">Rare</option>
<option value="mythic">Mythic</option>
</select>
</div>
</div>
</div>
)}
{/* Card List */}
<div className="flex-1 overflow-y-auto p-4">
{viewMode === 'list' ? (
/* List View */
<div className="space-y-1">
{searchLoading ? (
<div className="text-center py-8">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-accent-ember mx-auto"></div>
<p className="text-text-secondary mt-2 text-xs">Searching...</p>
</div>
) : searchResults.length === 0 ? (
<div className="text-center py-8">
<div className="text-2xl mb-2">🔍</div>
<p className="text-text-secondary text-xs">
{searchQuery ? 'No cards found' : 'No cards available'}
</p>
</div>
) : (
searchResults.map((card) => (
<div
key={card.id}
className="flex items-center p-2 bg-bg-secondary rounded hover:bg-bg-primary transition-colors cursor-pointer"
onClick={() => setSelectedCard(card)}
>
<div className="flex items-center space-x-2 flex-1 min-w-0">
{card.image_url && (
<img
src={card.image_url}
alt={card.name}
className="w-8 h-11 object-cover rounded flex-shrink-0"
/>
)}
<div className="min-w-0 flex-1">
<h4 className="font-medium text-text-primary text-xs truncate">{card.name}</h4>
<p className="text-text-secondary text-xs truncate">{card.set_name}</p>
<div className="flex items-center space-x-1">
{card.mana_cost && (
<ManaCost cost={card.mana_cost} size="xs" useSVG={manaSymbolSettings.useSVG} />
)}
{card.rarity && (
<span className={`text-xs px-1 rounded capitalize ${
card.rarity === 'mythic' ? 'bg-orange-100 text-orange-800' :
card.rarity === 'rare' ? 'bg-yellow-100 text-yellow-800' :
card.rarity === 'uncommon' ? 'bg-gray-100 text-gray-800' :
'bg-green-100 text-green-800'
}`}>
{card.rarity[0].toUpperCase()}
</span>
)}
</div>
</div>
</div>
</div>
))
)}
</div>
) : (
/* Thumbnail View */
<div>
{searchLoading ? (
<div className="text-center py-8">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-accent-ember mx-auto"></div>
<p className="text-text-secondary mt-2 text-xs">Searching...</p>
</div>
) : searchResults.length === 0 ? (
<div className="text-center py-8">
<div className="text-2xl mb-2">🔍</div>
<p className="text-text-secondary text-xs">
{searchQuery ? 'No cards found' : 'No cards available'}
</p>
</div>
) : (
<div className="grid grid-cols-3 gap-2">
{searchResults.map((card) => (
<div
key={card.id}
className="relative group cursor-pointer"
onClick={() => setSelectedCard(card)}
>
{card.image_url ? (
<div className="relative">
<img
src={card.image_url}
alt={card.name}
className="w-full aspect-[2.5/3.5] object-cover rounded-lg shadow-sm group-hover:shadow-md transition-shadow"
/>
{/* Hover overlay with card name */}
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-60 transition-all duration-200 rounded-lg flex items-end">
<div className="p-2 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<p className="text-xs font-medium truncate">{card.name}</p>
<p className="text-xs opacity-75 truncate">{card.set_name}</p>
</div>
</div>
{/* Rarity indicator */}
{card.rarity && (
<div className={`absolute top-1 right-1 w-2 h-2 rounded-full ${
card.rarity === 'mythic' ? 'bg-orange-500' :
card.rarity === 'rare' ? 'bg-yellow-500' :
card.rarity === 'uncommon' ? 'bg-gray-400' :
'bg-green-500'
}`}></div>
)}
</div>
) : (
<div className="w-full aspect-[2.5/3.5] bg-bg-secondary rounded-lg flex items-center justify-center group-hover:bg-bg-primary transition-colors">
<div className="text-center p-2">
<p className="text-xs font-medium text-text-primary truncate">{card.name}</p>
<p className="text-xs text-text-secondary truncate">{card.set_name}</p>
</div>
</div>
)}
</div>
))}
</div>
)}
</div>
)}
</div>
</>
) : (
/* Card Detail View */
<>
{/* Detail Header */}
<div className="flex items-center p-4 border-b border-border bg-bg-secondary rounded-t-lg">
<button
onClick={() => setSelectedCard(null)}
className="text-text-secondary hover:text-text-primary transition-colors mr-3"
>
Back
</button>
<h3 className="text-lg font-semibold text-text-primary truncate">{selectedCard.name}</h3>
</div>
{/* Card Detail Content */}
<div className="flex-1 overflow-y-auto p-4">
<div className="space-y-4">
{/* Card Image */}
{selectedCard.image_url && (
<div className="text-center">
<img
src={selectedCard.image_url}
alt={selectedCard.name}
className="w-full max-w-64 mx-auto rounded-lg shadow-lg"
/>
</div>
)}
{/* Card Info */}
<div className="space-y-3">
<div>
<h4 className="font-semibold text-text-primary text-lg">{selectedCard.name}</h4>
<p className="text-text-secondary text-sm">{selectedCard.set_name}</p>
</div>
{selectedCard.mana_cost && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Mana Cost</label>
<div className="bg-bg-primary px-3 py-2 rounded">
<ManaCost cost={selectedCard.mana_cost} size="md" useSVG={manaSymbolSettings.useSVG} />
</div>
</div>
)}
{selectedCard.card_type && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Type</label>
<div className="bg-bg-primary px-3 py-2 rounded text-sm">{selectedCard.card_type}</div>
</div>
)}
{selectedCard.oracle_text && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Oracle Text</label>
<div className="bg-bg-primary px-3 py-2 rounded text-sm whitespace-pre-wrap">{selectedCard.oracle_text}</div>
</div>
)}
<div className="grid grid-cols-2 gap-4">
{selectedCard.rarity && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">Rarity</label>
<div className={`px-3 py-2 rounded text-sm capitalize ${
selectedCard.rarity === 'mythic' ? 'bg-orange-100 text-orange-800' :
selectedCard.rarity === 'rare' ? 'bg-yellow-100 text-yellow-800' :
selectedCard.rarity === 'uncommon' ? 'bg-gray-100 text-gray-800' :
'bg-green-100 text-green-800'
}`}>
{selectedCard.rarity}
</div>
</div>
)}
{selectedCard.cmc !== undefined && (
<div>
<label className="block text-text-secondary text-xs font-medium mb-1">CMC</label>
<div className="bg-bg-primary px-3 py-2 rounded text-sm">{selectedCard.cmc}</div>
</div>
)}
</div>
</div>
</div>
</div>
</>
)}
{/* Floating Action Button */}
{selectedCard && (
<div className="absolute bottom-4 left-4 right-4">
<button
onClick={() => {
addCardToDeck(selectedCard);
setSelectedCard(null); setSelectedCard(null);
}} }}
className="w-full bg-accent-ember text-white py-3 rounded-lg hover:bg-accent-ember-dark transition-colors font-semibold shadow-lg" />
>
Add to Deck
</button>
</div>
)}
</div>
</div> </div>
</div> </div>
</div> </div>