refactor(design): site-wide sweep — broken Tailwind tokens, rounded corners, SearchBar primitive #117

Merged
varutasu merged 1 commit from design-sweep-pass into main 2026-06-04 15:06:22 -04:00
19 changed files with 1397 additions and 1188 deletions

View file

@ -194,7 +194,11 @@ export default function CardItem({
type="checkbox"
checked={isSelected}
onChange={handleSelectChange}
className="w-4 h-4 text-purple-600 bg-gray-100 border-gray-300 rounded focus:ring-purple-500 focus:ring-2"
className="w-4 h-4 rounded focus:ring-2"
style={{
accentColor: 'var(--accent-ember)',
'--tw-ring-color': 'var(--accent-ember)',
}}
/>
</div>

View file

@ -1,6 +1,7 @@
import CardItem from './CardItem';
import BulkSelectionToolbar from './BulkSelectionToolbar';
import CollectionSelectionModal from './CollectionSelectionModal';
import { Button, SearchBar } from './ui';
export default function CardsPageView(props) {
const {
@ -146,39 +147,28 @@ export default function CardsPageView(props) {
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
Search
</label>
<div className="relative">
<input
type="text"
placeholder="Search cards..."
className="search-bar w-full pr-12"
value={searchQuery}
onChange={(e) => handleSearchChange(e.target.value)}
onKeyPress={(e) => {
if (e.key === 'Enter') {
setPagination(prev => ({ ...prev, page: 1 }));
setCards([]);
setHasMore(true);
hasMoreRef.current = true;
fetchCards(false);
}
}}
/>
<button
onClick={() => {
<SearchBar
value={searchQuery}
onChange={(e) => handleSearchChange(e.target.value)}
onClear={() => {
handleSearchChange('');
setPagination(prev => ({ ...prev, page: 1 }));
setCards([]);
setHasMore(true);
hasMoreRef.current = true;
fetchCards(false);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setPagination(prev => ({ ...prev, page: 1 }));
setCards([]);
setHasMore(true);
hasMoreRef.current = true;
fetchCards(false);
}}
className="absolute right-2 top-1/2 transform -translate-y-1/2 p-2 rounded-lg hover:bg-opacity-20 transition-all duration-200"
style={{ backgroundColor: 'var(--bg-tertiary)' }}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
</div>
}
}}
placeholder="Search cards…"
/>
</div>
<div>
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
@ -358,12 +348,9 @@ export default function CardsPageView(props) {
{/* Load More Button */}
{hasMore && !loadingMore && (
<div className="flex justify-center py-8">
<button
onClick={loadMoreCards}
className="px-6 py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-lg hover:from-blue-600 hover:to-purple-700 transition-all duration-200 shadow-lg hover:shadow-xl"
>
<Button variant="primary" size="lg" onClick={loadMoreCards}>
Load More Cards
</button>
</Button>
</div>
)}

View file

@ -10,6 +10,7 @@ import ManaSymbolSettings from './ManaSymbolSettings';
import { VOCAB, collectionDisplayName } from '../lib/collection-vocabulary.js';
import CollectionEditModal from './CollectionEditModal';
import CollectionDeleteModal from './CollectionDeleteModal';
import { Button, SearchBar } from './ui';
export default function CollectionPageView(props) {
const {
cards,
@ -99,7 +100,7 @@ export default function CollectionPageView(props) {
<>
<button
onClick={() => setShowEditModal(true)}
className="px-4 py-2 text-sm font-medium border rounded-lg hover:bg-gray-50 flex items-center space-x-2"
className="px-4 py-2 text-sm font-medium border rounded-xl nav-item-hover flex items-center space-x-2"
style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -109,7 +110,7 @@ export default function CollectionPageView(props) {
</button>
<button
onClick={() => setShowDeleteModal(true)}
className="px-4 py-2 text-sm font-medium border rounded-lg hover:bg-red-50 hover:border-red-200 hover:text-red-600 flex items-center space-x-2"
className="px-4 py-2 text-sm font-medium border rounded-xl nav-item-hover flex items-center space-x-2"
style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -122,12 +123,15 @@ export default function CollectionPageView(props) {
<button
onClick={() => setShowUploadModal(true)}
className="px-4 py-2 text-sm font-medium border rounded-lg hover:bg-gray-50"
className="px-4 py-2 text-sm font-medium border rounded-xl nav-item-hover"
style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}
>
Upload Image
</button>
<button className="px-4 py-2 text-sm font-medium border rounded-lg hover:bg-gray-50" style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}>
<button
className="px-4 py-2 text-sm font-medium border rounded-xl nav-item-hover"
style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}
>
🪄 Generate AI Image
</button>
</div>
@ -142,16 +146,31 @@ export default function CollectionPageView(props) {
{/* System collection indicator */}
{collection.isSystemCollection && (
<div className="flex items-center space-x-1">
<span className="px-2.5 py-1 text-xs font-bold rounded-full bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-sm border-2 border-blue-200">
<span
className="px-2.5 py-1 text-xs font-bold rounded-full text-white shadow-sm border-2"
style={{
background:
'linear-gradient(135deg, var(--accent-ember) 0%, var(--accent-flame) 100%)',
borderColor: 'var(--accent-flame)',
}}
>
🔒 SYSTEM
</span>
<div className="group relative">
<svg className="h-4 w-4 text-blue-500 cursor-help" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg
className="h-4 w-4 cursor-help"
style={{ color: 'var(--accent-ember)' }}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 text-xs bg-gray-900 text-white rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity z-10 whitespace-nowrap">
<div
className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 text-xs rounded-xl shadow-lg opacity-0 group-hover:opacity-100 transition-opacity z-10 whitespace-nowrap glass-panel-strong"
style={{ color: 'var(--text-primary)' }}
>
{VOCAB.SYSTEM_COLLECTION_SYNC_HINT}
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>
</div>
</div>
</div>
@ -167,7 +186,11 @@ export default function CollectionPageView(props) {
count > 0 ? (
<span
key={game}
className="px-3 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800"
className="px-3 py-1 text-xs font-medium rounded-full"
style={{
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-primary)',
}}
>
{game}
</span>
@ -202,7 +225,7 @@ export default function CollectionPageView(props) {
<div className="flex items-center space-x-4">
<button
onClick={() => setShowShareModal(true)}
className="flex items-center space-x-2 px-3 py-2 text-sm font-medium border rounded-lg hover:bg-gray-50"
className="flex items-center space-x-2 px-3 py-2 text-sm font-medium border rounded-xl nav-item-hover"
style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -213,10 +236,18 @@ export default function CollectionPageView(props) {
<button
onClick={toggleFavorite}
className={`flex items-center space-x-2 px-3 py-2 text-sm font-medium border rounded-lg ${
isFavorited ? 'bg-red-50 border-red-200 text-red-600' : 'hover:bg-gray-50'
className={`flex items-center space-x-2 px-3 py-2 text-sm font-medium border rounded-xl ${
isFavorited ? '' : 'nav-item-hover'
}`}
style={!isFavorited ? { borderColor: 'var(--border)', color: 'var(--text-primary)' } : {}}
style={
isFavorited
? {
borderColor: 'var(--accent-ember)',
color: 'var(--accent-ember)',
backgroundColor: 'transparent',
}
: { borderColor: 'var(--border)', color: 'var(--text-primary)' }
}
>
<svg className="w-4 h-4" fill={isFavorited ? 'currentColor' : 'none'} stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
@ -226,7 +257,7 @@ export default function CollectionPageView(props) {
<button
onClick={handleDownloadCSV}
className="flex items-center space-x-2 px-3 py-2 text-sm font-medium border rounded-lg hover:bg-gray-50"
className="flex items-center space-x-2 px-3 py-2 text-sm font-medium border rounded-xl nav-item-hover"
style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -238,7 +269,15 @@ export default function CollectionPageView(props) {
<div className="flex items-center space-x-1 text-sm" style={{ color: 'var(--text-secondary)' }}>
<span>Activity</span>
<span className="px-2 py-1 bg-purple-100 text-purple-800 rounded-full text-xs font-medium">123</span>
<span
className="px-2 py-1 rounded-full text-xs font-medium"
style={{
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-primary)',
}}
>
123
</span>
</div>
</div>
</div>
@ -248,28 +287,25 @@ export default function CollectionPageView(props) {
{/* Search and Filters */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-4">
<div className="relative">
<input
type="text"
placeholder="Search cards..."
<div className="w-64">
<SearchBar
value={searchCards}
onChange={(e) => {
setSearchCards(e.target.value);
handleSearchCards(e.target.value);
}}
className="w-64 px-4 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
onClear={() => {
setSearchCards('');
handleSearchCards('');
}}
placeholder="Search cards…"
/>
<svg className="absolute right-3 top-2.5 w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: 'var(--text-secondary)' }}>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<select
value={selectedRarity}
onChange={(e) => setSelectedRarity(e.target.value)}
className="px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500"
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
className="input-field"
>
<option>All Rarities</option>
<option>Common</option>
@ -282,8 +318,7 @@ export default function CollectionPageView(props) {
<select
value={selectedType}
onChange={(e) => setSelectedType(e.target.value)}
className="px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500"
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
className="input-field"
>
<option>All Types</option>
<option>Creature</option>
@ -296,8 +331,7 @@ export default function CollectionPageView(props) {
<select
value={groupBy}
onChange={(e) => setGroupBy(e.target.value)}
className="px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500"
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
className="input-field"
>
<option>Group by Game</option>
<option>Group by Rarity</option>
@ -307,8 +341,7 @@ export default function CollectionPageView(props) {
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500"
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-secondary)' }}
className="input-field"
>
<option>Sort by Name</option>
<option>Sort by Price</option>
@ -318,11 +351,22 @@ export default function CollectionPageView(props) {
</div>
<div className="flex items-center space-x-3">
<div className="flex rounded-lg border" style={{ borderColor: 'var(--border)' }}>
<div
className="flex rounded-xl border"
style={{ borderColor: 'var(--border)' }}
>
<button
onClick={() => setViewMode('grid')}
className={`p-2 rounded-l-lg ${viewMode === 'grid' ? 'bg-white text-gray-900 shadow-sm' : 'bg-transparent hover:bg-gray-50'}`}
style={viewMode !== 'grid' ? { color: 'var(--text-secondary)' } : {}}
className="p-2 rounded-l-xl transition-colors"
style={{
backgroundColor:
viewMode === 'grid' ? 'var(--bg-tertiary)' : 'transparent',
color:
viewMode === 'grid'
? 'var(--text-primary)'
: 'var(--text-secondary)',
}}
aria-label="Grid view"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
@ -330,8 +374,16 @@ export default function CollectionPageView(props) {
</button>
<button
onClick={() => setViewMode('list')}
className={`p-2 rounded-r-lg ${viewMode === 'list' ? 'bg-white text-gray-900 shadow-sm' : 'bg-transparent hover:bg-gray-50'}`}
style={viewMode !== 'list' ? { color: 'var(--text-secondary)' } : {}}
className="p-2 rounded-r-xl transition-colors"
style={{
backgroundColor:
viewMode === 'list' ? 'var(--bg-tertiary)' : 'transparent',
color:
viewMode === 'list'
? 'var(--text-primary)'
: 'var(--text-secondary)',
}}
aria-label="List view"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 10h16M4 14h16M4 18h16" />
@ -339,18 +391,22 @@ export default function CollectionPageView(props) {
</button>
</div>
<button
<Button
variant="primary"
onClick={() => router.push('/cards')}
className="px-4 py-2 text-white rounded-lg flex items-center space-x-2"
style={{ backgroundColor: 'var(--accent-ember)' }}
leadingIcon={
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span>Add Cards</span>
</button>
Add Cards
</Button>
<button className="px-4 py-2 border rounded-lg hover:bg-gray-50 flex items-center space-x-2" style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}>
<button
className="px-4 py-2 border rounded-xl nav-item-hover flex items-center space-x-2"
style={{ borderColor: 'var(--border)', color: 'var(--text-primary)' }}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
@ -366,7 +422,13 @@ export default function CollectionPageView(props) {
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold flex items-center space-x-3" style={{ color: 'var(--text-primary)' }}>
<span>{game === 'MTG' ? 'Magic The Gathering' : game}</span>
<span className="px-2 py-1 bg-gray-100 text-gray-600 rounded text-sm font-medium">
<span
className="px-2 py-1 rounded-xl text-sm font-medium"
style={{
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-secondary)',
}}
>
{gameCards.length}
</span>
</h2>
@ -399,21 +461,23 @@ export default function CollectionPageView(props) {
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
Add cards to get started with your collection
</p>
<button className="px-6 py-3 text-white rounded-lg" style={{ backgroundColor: 'var(--accent-ember)' }}>
<Button variant="primary" onClick={() => router.push('/cards')}>
Browse Cards to Add
</button>
</Button>
</div>
)}
</div>
{/* Search Results Dropdown */}
{showSearchResults && searchResults.length > 0 && (
<div className="absolute top-full left-0 right-0 bg-white border border-gray-200 rounded-lg shadow-lg max-h-64 overflow-y-auto z-50">
<div
className="absolute top-full left-0 right-0 glass-panel-strong rounded-xl max-h-64 overflow-y-auto z-50"
>
{searchResults.map(card => (
<div
key={card.id}
onClick={() => handleAddCard(card)}
className="flex items-center p-3 hover:bg-gray-50 cursor-pointer"
className="flex items-center p-3 cursor-pointer nav-item-hover"
>
<img
src={card.image_url}
@ -424,8 +488,12 @@ export default function CollectionPageView(props) {
}}
/>
<div>
<div className="font-medium">{card.name}</div>
<div className="text-sm text-gray-500">{card.set_name} ${card.market_price}</div>
<div className="font-medium" style={{ color: 'var(--text-primary)' }}>
{card.name}
</div>
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
{card.set_name} ${card.market_price}
</div>
</div>
</div>
))}

View file

@ -140,16 +140,31 @@ export default function CollectionsPageView(props) {
{/* System collection indicator */}
{collection.isSystemCollection && (
<div className="flex items-center space-x-1">
<span className="px-2.5 py-1 text-xs font-bold rounded-full bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-sm border-2 border-blue-200">
<span
className="px-2.5 py-1 text-xs font-bold rounded-full text-white shadow-sm border-2"
style={{
background:
'linear-gradient(135deg, var(--accent-ember) 0%, var(--accent-flame) 100%)',
borderColor: 'var(--accent-flame)',
}}
>
🔒 SYSTEM
</span>
<div className="group relative">
<svg className="h-4 w-4 text-blue-500 cursor-help" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg
className="h-4 w-4 cursor-help"
style={{ color: 'var(--accent-ember)' }}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 text-xs bg-gray-900 text-white rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity z-10 whitespace-nowrap">
<div
className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 text-xs rounded-xl shadow-lg opacity-0 group-hover:opacity-100 transition-opacity z-10 whitespace-nowrap glass-panel-strong"
style={{ color: 'var(--text-primary)' }}
>
{VOCAB.SYSTEM_COLLECTION_SYNC_HINT}
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>
</div>
</div>
</div>

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,27 @@
/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
import { ManaCost } from './ManaSymbols';
import { Button } from './ui';
/* 2026-06-04 design-sweep pass: same broken Tailwind tokens
(bg-bg-*, text-text-*, bg-accent-ember, hover:bg-accent-ember-dark)
that the other deck builder files had none of those classes are
defined in tailwind.config.js, so this list rendered with no
visible card surfaces or hover affordances. Sweep replaces with
inline CSS variables + Button primitive. Rarity badges reuse the
neutral palette from DeckBuilderCardBrowser. */
const rarityBadgeStyle = (rarity) => {
switch (rarity) {
case 'mythic':
return { backgroundColor: 'var(--accent-ember)', color: '#ffffff' };
case 'rare':
return { backgroundColor: 'var(--accent-flame)', color: '#ffffff' };
case 'uncommon':
return { backgroundColor: 'var(--bg-tertiary)', color: 'var(--text-primary)' };
default:
return { backgroundColor: 'var(--bg-tertiary)', color: 'var(--text-secondary)' };
}
};
export default function DeckBuilderDeckList({
deckCards,
@ -10,76 +32,112 @@ export default function DeckBuilderDeckList({
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"
<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 mb-2"
style={{ color: 'var(--text-primary)' }}
>
Empty Deck
</h3>
<p className="mb-4" style={{ color: 'var(--text-secondary)' }}>
Start building your deck by searching for cards
</p>
{!sidebarOpen && (
<Button variant="primary" onClick={onOpenSidebar}>
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 rounded-xl transition-colors nav-item-hover"
style={{ backgroundColor: 'var(--bg-primary)' }}
>
<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-lg"
style={{ color: 'var(--text-primary)' }}
>
{card.name}
</h4>
<p
className="text-sm"
style={{ color: 'var(--text-secondary)' }}
>
{card.set_name}
</p>
<div className="flex items-center space-x-3 mt-1">
{card.mana_cost && (
<div
className="px-2 py-1 rounded"
style={{ backgroundColor: 'var(--bg-secondary)' }}
>
Open Card Browser
</button>
<ManaCost
cost={card.mana_cost}
size="sm"
useSVG={manaSymbolSettings.useSVG}
/>
</div>
)}
{card.rarity && (
<span
className="text-xs px-2 py-1 rounded capitalize"
style={rarityBadgeStyle(card.rarity)}
>
{card.rarity}
</span>
)}
</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>
</div>
<div className="flex items-center space-x-3">
<span
className="font-bold text-lg"
style={{ color: 'var(--text-primary)' }}
>
{card.quantity}x
</span>
<div className="flex items-center space-x-1">
<button
onClick={() => onRemoveCard(card.card_id, 1)}
className="w-8 h-8 rounded-full transition-colors flex items-center justify-center font-bold text-white"
style={{ backgroundColor: '#dc2626' }}
aria-label="Remove one"
>
</button>
<button
onClick={() => onAddCard(card, 1)}
className="w-8 h-8 rounded-full transition-transform hover:scale-105 active:scale-95 flex items-center justify-center font-bold text-white"
style={{
background:
'linear-gradient(135deg, var(--accent-ember) 0%, var(--accent-flame) 100%)',
boxShadow: 'var(--rim-light-inner), var(--ember-rim-subtle)',
}}
aria-label="Add one"
>
+
</button>
</div>
</div>
</div>
))
)}
</div>
);
}

View file

@ -4,26 +4,41 @@
*/
export default function DeckBuilderStatsBar({ stats }) {
return (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6 p-4 bg-bg-primary rounded-lg">
<div
className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6 p-4 rounded-xl"
style={{ backgroundColor: 'var(--bg-primary)' }}
>
<div className="text-center">
<div className="text-lg font-bold text-text-primary">{stats.totalCards}/100</div>
<div className="text-text-secondary text-sm">Cards</div>
<div className="text-lg font-bold" style={{ color: 'var(--text-primary)' }}>
{stats.totalCards}/100
</div>
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Cards
</div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-text-primary">{stats.avgCmc}</div>
<div className="text-text-secondary text-sm">Avg CMC</div>
<div className="text-lg font-bold" style={{ color: 'var(--text-primary)' }}>
{stats.avgCmc}
</div>
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Avg CMC
</div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-text-primary">
<div className="text-lg font-bold" style={{ color: 'var(--text-primary)' }}>
{Object.keys(stats.colorCounts).length}
</div>
<div className="text-text-secondary text-sm">Colors</div>
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Colors
</div>
</div>
<div className="text-center">
<div className="text-lg font-bold text-text-primary">
<div className="text-lg font-bold" style={{ color: 'var(--text-primary)' }}>
{Object.keys(stats.typeCounts).length}
</div>
<div className="text-text-secondary text-sm">Types</div>
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Types
</div>
</div>
</div>
);

View file

@ -2,8 +2,16 @@ import Link from 'next/link';
import DeckBuilderCardBrowser from './DeckBuilderCardBrowser';
import DeckBuilderDeckList from './DeckBuilderDeckList';
import DeckBuilderStatsBar from './DeckBuilderStatsBar';
import { Button } from './ui';
import { computeDeckStats } from '../lib/deck-builder-stats';
/* 2026-06-04 design-sweep pass: replaced broken Tailwind token
classes (bg-bg-secondary, text-text-primary, bg-accent-ember, etc.
none of which are defined in tailwind.config.js and produced
zero CSS) with inline style={{ ... CSS vars ... }} + the Button
primitive + `glass-panel` + `rounded-2xl` so this view actually
renders with the Liquid Glass design system. */
export default function DeckBuilderView({
addCardToDeck,
clearFilters,
@ -37,46 +45,46 @@ export default function DeckBuilderView({
<div className="flex justify-between items-center mb-6">
<div>
<div className="flex items-center space-x-3">
<Link href="/decks" className="text-accent-ember hover:underline">
<Link
href="/decks"
className="hover:underline"
style={{ color: 'var(--accent-ember)' }}
>
Back to Decks
</Link>
</div>
<h1 className="text-3xl font-bold text-text-primary mt-2">{deck.name}</h1>
<p className="text-text-secondary">
<h1 className="text-3xl font-bold mt-2" style={{ color: 'var(--text-primary)' }}>
{deck.name}
</h1>
<p style={{ color: 'var(--text-secondary)' }}>
{deck.format} {stats.totalCards}/100 cards
</p>
</div>
<div className="flex space-x-3">
<button
type="button"
className="bg-bg-secondary text-text-primary px-4 py-2 rounded-lg hover:bg-bg-tertiary transition-colors"
>
<Button type="button" variant="secondary">
Save Deck
</button>
<Link
href={`/deck/${deck.id}`}
className="bg-accent-ember text-white px-4 py-2 rounded-lg hover:bg-accent-ember-dark transition-colors"
>
View Deck
</Button>
<Link href={`/deck/${deck.id}`}>
<Button variant="primary">View Deck</Button>
</Link>
</div>
</div>
<div className="flex gap-6 h-[calc(100vh-12rem)]">
<div className="flex-1 transition-all duration-300">
<div className="bg-bg-secondary rounded-lg p-6 h-full flex flex-col">
<div className="glass-panel rounded-2xl p-6 h-full flex flex-col">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-semibold text-text-primary">
<h2 className="text-xl font-semibold" style={{ color: 'var(--text-primary)' }}>
Deck Cards ({stats.totalCards})
</h2>
<button
<Button
type="button"
variant="primary"
onClick={() => setSidebarOpen(!sidebarOpen)}
className="bg-accent-ember text-white px-4 py-2 rounded-lg hover:bg-accent-ember-dark transition-colors flex items-center space-x-2"
trailingIcon={<span aria-hidden="true">{sidebarOpen ? '→' : '←'}</span>}
>
<span>{sidebarOpen ? 'Hide' : 'Show'} Browser</span>
<span>{sidebarOpen ? '→' : '←'}</span>
</button>
{sidebarOpen ? 'Hide' : 'Show'} Browser
</Button>
</div>
<DeckBuilderStatsBar stats={stats} />

View file

@ -99,11 +99,7 @@ function UserProfileDropdown({ user, onMobileMenuClose }) {
{profileMenuItems.map((item) => (
<Link key={item.name} href={item.href}>
<div
className={`
flex items-center px-4 py-3 text-sm transition-all duration-200 cursor-pointer
hover:bg-gray-50 dark:hover:bg-gray-700
${item.isLogout ? 'text-red-600 dark:text-red-400' : ''}
`}
className="flex items-center px-4 py-3 text-sm cursor-pointer nav-item-hover"
style={{
color: item.isLogout ? 'var(--accent-ember)' : 'var(--text-primary)'
}}

View file

@ -31,18 +31,27 @@ export default function ManaSymbolSettings({ onSettingsChange }) {
};
return (
<div className="flex items-center space-x-3 p-3 bg-bg-secondary rounded-lg">
<div
className="flex items-center space-x-3 p-3 rounded-xl"
style={{ backgroundColor: 'var(--bg-secondary)' }}
>
<div className="flex-1">
<h4 className="text-sm font-medium text-text-primary">Mana Symbol Style</h4>
<p className="text-xs text-text-secondary">
<h4
className="text-sm font-medium"
style={{ color: 'var(--text-primary)' }}
>
Mana Symbol Style
</h4>
<p className="text-xs" style={{ color: 'var(--text-secondary)' }}>
{useSVG ? 'Using official Scryfall SVG symbols' : 'Using custom circular symbols'}
</p>
</div>
<button
onClick={handleToggle}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
useSVG ? 'bg-accent-ember' : 'bg-gray-300'
}`}
className="relative inline-flex h-6 w-11 items-center rounded-full transition-colors"
style={{
backgroundColor: useSVG ? 'var(--accent-ember)' : 'var(--bg-tertiary)',
}}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${

View file

@ -212,7 +212,7 @@ export function AdvancedManaCost({ cost, showAnalysis = false, useSVG = false })
<div className="space-y-2">
<ManaCost cost={analysis.cost} useSVG={useSVG} />
{showAnalysis && (
<div className="text-xs text-text-secondary space-y-1">
<div className="text-xs space-y-1" style={{ color: 'var(--text-secondary)' }}>
<div>CMC: {analysis.cmc}</div>
{analysis.colors.length > 0 && (
<div className="flex items-center space-x-1">

View file

@ -184,21 +184,33 @@ export default function ShareModal({
{/* Search Results */}
{searchResults.length > 0 && (
<div className="mt-2 border border-gray-200 rounded-lg max-h-40 overflow-y-auto">
<div
className="mt-2 rounded-xl max-h-40 overflow-y-auto glass-panel"
>
{searchResults.map((user) => (
<div
key={user.id}
onClick={() => handleInvite(user)}
className="flex items-center p-3 hover:bg-gray-50 cursor-pointer"
className="flex items-center p-3 cursor-pointer nav-item-hover"
>
<div className="w-8 h-8 bg-purple-600 rounded-full flex items-center justify-center mr-3">
<div
className="w-8 h-8 rounded-full flex items-center justify-center mr-3"
style={{
background:
'linear-gradient(135deg, var(--accent-ember) 0%, var(--accent-flame) 100%)',
}}
>
<span className="text-white text-sm font-bold">
{user.email.charAt(0).toUpperCase()}
</span>
</div>
<div>
<div className="font-medium text-gray-900">{user.email}</div>
<div className="text-sm text-gray-500">Click to invite as viewer</div>
<div className="font-medium" style={{ color: 'var(--text-primary)' }}>
{user.email}
</div>
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Click to invite as viewer
</div>
</div>
</div>
))}
@ -207,19 +219,32 @@ export default function ShareModal({
{/* Email invite option */}
{searchQuery && isValidEmail(searchQuery) && !searchResults.some(u => u.email === searchQuery) && (
<div className="mt-2 border border-gray-200 rounded-lg">
<div className="mt-2 rounded-xl glass-panel">
<div
onClick={() => handleInvite(searchQuery)}
className="flex items-center p-3 hover:bg-gray-50 cursor-pointer"
className="flex items-center p-3 cursor-pointer nav-item-hover"
>
<div className="w-8 h-8 bg-gray-400 rounded-full flex items-center justify-center mr-3">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div
className="w-8 h-8 rounded-full flex items-center justify-center mr-3"
style={{ backgroundColor: 'var(--bg-tertiary)' }}
>
<svg
className="w-4 h-4"
style={{ color: 'var(--text-primary)' }}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207" />
</svg>
</div>
<div>
<div className="font-medium text-gray-900">Invite {searchQuery}</div>
<div className="text-sm text-gray-500">Send email invitation as viewer</div>
<div className="font-medium" style={{ color: 'var(--text-primary)' }}>
Invite {searchQuery}
</div>
<div className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Send email invitation as viewer
</div>
</div>
</div>
</div>
@ -310,7 +335,8 @@ export default function ShareModal({
<button
key={social.platform}
onClick={() => handleSocialShare(social.platform)}
className="flex flex-col items-center p-3 rounded-lg border hover:bg-gray-50 transition-colors"
className="flex flex-col items-center p-3 rounded-xl border nav-item-hover transition-colors"
style={{ borderColor: 'var(--border)' }}
>
<div className="w-8 h-8 mb-2 text-gray-600">
{social.icon === 'twitter' && (

View file

@ -4,7 +4,7 @@ import { useRouter } from 'next/router';
import Link from 'next/link';
import Layout from '../../components/Layout';
import PermissionIndicator from '../../components/PermissionIndicator';
import { Button } from '../../components/ui';
import { Button, SearchBar } from '../../components/ui';
import { useAuth } from '../../lib/use-auth';
import { VOCAB } from '../../lib/collection-vocabulary.js';
@ -213,12 +213,11 @@ export default function CommunityCollections() {
{/* Search and Sort */}
<div className="flex items-center justify-between mt-6">
<div className="flex-1 max-w-md">
<input
type="text"
placeholder="Search lists..."
className="input-field w-full"
<SearchBar
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onClear={() => setSearchQuery('')}
placeholder="Search lists…"
/>
</div>
<div className="flex gap-4">
@ -311,10 +310,10 @@ export default function CommunityCollections() {
{collection.tags.slice(0, 2).map((tag, index) => (
<span
key={index}
className="px-2 py-1 rounded-md text-xs font-medium"
className="px-2 py-1 rounded-xl text-xs font-medium"
style={{
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-secondary)'
color: 'var(--text-secondary)',
}}
>
{tag}
@ -322,10 +321,10 @@ export default function CommunityCollections() {
))}
{collection.tags.length > 2 && (
<span
className="px-2 py-1 rounded-md text-xs font-medium"
className="px-2 py-1 rounded-xl text-xs font-medium"
style={{
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-secondary)'
color: 'var(--text-secondary)',
}}
>
+{collection.tags.length - 2}

View file

@ -27,7 +27,10 @@ export default function DeckBuilder() {
return (
<Layout user={user}>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-accent-ember" />
<div
className="animate-spin rounded-full h-32 w-32 border-b-2"
style={{ borderColor: 'var(--accent-ember)' }}
/>
</div>
</Layout>
);

View file

@ -4,9 +4,18 @@ import { useRouter } from 'next/router';
import Link from 'next/link';
import Layout from '../../components/Layout';
import { ManaCost, ColorIdentity } from '../../components/ManaSymbols';
import { Button } from '../../components/ui';
import { useAuth } from '../../lib/use-auth';
import { getColorIdentity } from '../../lib/mana-symbols';
/* 2026-06-04 design-sweep pass: this page used the broken Tailwind
token classes (bg-bg-*, text-text-*, bg-accent-ember,
hover:bg-accent-ember-dark, border-accent-ember) that don't
resolve in tailwind.config.js. Sweep replaces them with inline
CSS variables + Button primitive + glass-panel surface + the
nav-item-active / nav-item-hover utilities so the page renders
with the Liquid Glass design system. */
export default function DeckDetail() {
const { user } = useAuth();
const router = useRouter();
@ -136,7 +145,10 @@ export default function DeckDetail() {
return (
<Layout user={user}>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-accent-ember"></div>
<div
className="animate-spin rounded-full h-32 w-32 border-b-2"
style={{ borderColor: 'var(--accent-ember)' }}
/>
</div>
</Layout>
);
@ -147,8 +159,17 @@ export default function DeckDetail() {
<Layout user={user}>
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<h1 className="text-2xl font-bold mb-4">Deck not found</h1>
<Link href="/decks" className="text-accent-ember hover:underline">
<h1
className="text-2xl font-bold mb-4"
style={{ color: 'var(--text-primary)' }}
>
Deck not found
</h1>
<Link
href="/decks"
className="hover:underline"
style={{ color: 'var(--accent-ember)' }}
>
Back to Decks
</Link>
</div>
@ -168,34 +189,51 @@ export default function DeckDetail() {
<div className="flex justify-between items-start mb-8">
<div>
<div className="flex items-center space-x-3 mb-2">
<Link href="/decks" className="text-accent-ember hover:underline">
<Link
href="/decks"
className="hover:underline"
style={{ color: 'var(--accent-ember)' }}
>
Back to Decks
</Link>
</div>
<div className="flex items-center space-x-3 mb-2">
<span className="text-3xl">{getFormatIcon(deck.format)}</span>
<h1 className="text-3xl font-bold text-text-primary">{deck.name}</h1>
<h1
className="text-3xl font-bold"
style={{ color: 'var(--text-primary)' }}
>
{deck.name}
</h1>
{deck.is_public && (
<span className="bg-green-100 text-green-800 px-2 py-1 rounded-full text-xs font-medium">
<span
className="px-2 py-1 rounded-full text-xs font-medium"
style={{
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--accent-ember)',
}}
>
Public
</span>
)}
</div>
<p className="text-text-secondary mb-2">
<p className="mb-2" style={{ color: 'var(--text-secondary)' }}>
by {deck.creator_username} {deck.format} {stats.totalCards} cards
</p>
{deck.description && (
<p className="text-text-secondary max-w-2xl">{deck.description}</p>
<p
className="max-w-2xl"
style={{ color: 'var(--text-secondary)' }}
>
{deck.description}
</p>
)}
</div>
{isOwner && (
<div className="flex space-x-3">
<Link
href={`/deck-builder?deck=${deck.id}`}
className="bg-accent-ember text-white px-4 py-2 rounded-lg hover:bg-accent-ember-dark transition-colors"
>
Edit Deck
<Link href={`/deck-builder?deck=${deck.id}`}>
<Button variant="primary">Edit Deck</Button>
</Link>
</div>
)}
@ -204,38 +242,64 @@ export default function DeckDetail() {
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Stats Sidebar */}
<div className="lg:col-span-1">
<div className="bg-bg-secondary rounded-lg p-6 mb-6">
<h3 className="text-lg font-semibold text-text-primary mb-4">Statistics</h3>
<div className="glass-panel rounded-2xl p-6 mb-6">
<h3
className="text-lg font-semibold mb-4"
style={{ color: 'var(--text-primary)' }}
>
Statistics
</h3>
<div className="space-y-3">
<div className="flex justify-between">
<span className="text-text-secondary">Total Cards:</span>
<span className="text-text-primary font-medium">{stats.totalCards}</span>
<span style={{ color: 'var(--text-secondary)' }}>Total Cards:</span>
<span className="font-medium" style={{ color: 'var(--text-primary)' }}>
{stats.totalCards}
</span>
</div>
<div className="flex justify-between">
<span className="text-text-secondary">Avg. CMC:</span>
<span className="text-text-primary font-medium">{stats.avgCmc}</span>
<span style={{ color: 'var(--text-secondary)' }}>Avg. CMC:</span>
<span className="font-medium" style={{ color: 'var(--text-primary)' }}>
{stats.avgCmc}
</span>
</div>
<div className="flex justify-between">
<span className="text-text-secondary">Format:</span>
<span className="text-text-primary font-medium">{deck.format}</span>
<span style={{ color: 'var(--text-secondary)' }}>Format:</span>
<span className="font-medium" style={{ color: 'var(--text-primary)' }}>
{deck.format}
</span>
</div>
</div>
{/* Color Distribution */}
{Object.keys(stats.colorCounts).length > 0 && (
<div className="mt-6">
<h4 className="text-text-secondary text-sm font-medium mb-3">Color Distribution</h4>
<h4
className="text-sm font-medium mb-3"
style={{ color: 'var(--text-secondary)' }}
>
Color Distribution
</h4>
<div className="space-y-2">
{Object.entries(stats.colorCounts)
.sort(([,a], [,b]) => b - a)
.sort(([, a], [, b]) => b - a)
.map(([color, count]) => (
<div key={color} className="flex justify-between items-center">
<div className="flex items-center space-x-2">
<span className="text-lg">{color}</span>
<span className="text-text-secondary text-sm">{color}</span>
<span
className="text-sm"
style={{ color: 'var(--text-secondary)' }}
>
{color}
</span>
</div>
<span className="text-text-primary font-medium">{count}</span>
<span
className="font-medium"
style={{ color: 'var(--text-primary)' }}
>
{count}
</span>
</div>
))}
</div>
@ -245,15 +309,30 @@ export default function DeckDetail() {
{/* Type Distribution */}
{Object.keys(stats.typeCounts).length > 0 && (
<div className="mt-6">
<h4 className="text-text-secondary text-sm font-medium mb-3">Card Types</h4>
<h4
className="text-sm font-medium mb-3"
style={{ color: 'var(--text-secondary)' }}
>
Card Types
</h4>
<div className="space-y-2">
{Object.entries(stats.typeCounts)
.sort(([,a], [,b]) => b - a)
.sort(([, a], [, b]) => b - a)
.slice(0, 8)
.map(([type, count]) => (
<div key={type} className="flex justify-between">
<span className="text-text-secondary text-sm">{type}</span>
<span className="text-text-primary font-medium">{count}</span>
<span
className="text-sm"
style={{ color: 'var(--text-secondary)' }}
>
{type}
</span>
<span
className="font-medium"
style={{ color: 'var(--text-primary)' }}
>
{count}
</span>
</div>
))}
</div>
@ -262,22 +341,25 @@ export default function DeckDetail() {
</div>
{/* Group By Controls */}
<div className="bg-bg-secondary rounded-lg p-6">
<h3 className="text-lg font-semibold text-text-primary mb-4">Group Cards By</h3>
<div className="glass-panel rounded-2xl p-6">
<h3
className="text-lg font-semibold mb-4"
style={{ color: 'var(--text-primary)' }}
>
Group Cards By
</h3>
<div className="space-y-2">
{[
{ value: 'type', label: 'Card Type' },
{ value: 'cmc', label: 'Mana Cost' },
{ value: 'color', label: 'Color' },
{ value: 'rarity', label: 'Rarity' }
{ value: 'rarity', label: 'Rarity' },
].map(option => (
<button
key={option.value}
onClick={() => setGroupBy(option.value)}
className={`w-full text-left px-3 py-2 rounded-lg transition-colors ${
groupBy === option.value
? 'bg-accent-ember text-white'
: 'text-text-secondary hover:bg-bg-tertiary'
className={`nav-item w-full text-left px-3 py-2 ${
groupBy === option.value ? 'nav-item-active' : 'nav-item-hover'
}`}
>
{option.label}
@ -289,12 +371,19 @@ export default function DeckDetail() {
{/* Card List */}
<div className="lg:col-span-3">
<div className="bg-bg-secondary rounded-lg p-6">
<div className="glass-panel rounded-2xl p-6">
{deck.cards && deck.cards.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">This deck doesn&apos;t have any cards yet</p>
<h3
className="text-xl font-semibold mb-2"
style={{ color: 'var(--text-primary)' }}
>
Empty Deck
</h3>
<p style={{ color: 'var(--text-secondary)' }}>
This deck doesn&apos;t have any cards yet
</p>
</div>
) : (
<div className="space-y-6">
@ -302,14 +391,24 @@ export default function DeckDetail() {
.sort(([a], [b]) => a.localeCompare(b))
.map(([group, cards]) => (
<div key={group}>
<h3 className="text-lg font-semibold text-text-primary mb-3 border-b border-border pb-2">
<h3
className="text-lg font-semibold mb-3 border-b pb-2"
style={{
color: 'var(--text-primary)',
borderColor: 'var(--border)',
}}
>
{group} ({cards.reduce((sum, card) => sum + card.quantity, 0)})
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{cards
.sort((a, b) => a.name.localeCompare(b.name))
.map((card) => (
<div key={`${card.card_id}-${card.id}`} className="flex items-center space-x-3 p-3 bg-bg-primary rounded-lg hover:bg-bg-tertiary transition-colors">
<div
key={`${card.card_id}-${card.id}`}
className="flex items-center space-x-3 p-3 rounded-xl transition-colors nav-item-hover"
style={{ backgroundColor: 'var(--bg-primary)' }}
>
{card.image_url && (
<img
src={card.image_url}
@ -319,15 +418,35 @@ export default function DeckDetail() {
)}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<h4 className="font-medium text-text-primary truncate">{card.name}</h4>
<span className="text-text-primary font-medium ml-2">{card.quantity}x</span>
<h4
className="font-medium truncate"
style={{ color: 'var(--text-primary)' }}
>
{card.name}
</h4>
<span
className="font-medium ml-2"
style={{ color: 'var(--text-primary)' }}
>
{card.quantity}x
</span>
</div>
<p className="text-text-secondary text-sm">{card.set_name}</p>
<div className="flex items-center space-x-2 text-xs text-text-secondary">
<p
className="text-sm"
style={{ color: 'var(--text-secondary)' }}
>
{card.set_name}
</p>
<div
className="flex items-center space-x-2 text-xs"
style={{ color: 'var(--text-secondary)' }}
>
{card.mana_cost && (
<ManaCost cost={card.mana_cost} size="xs" />
)}
{card.rarity && <span className="capitalize">{card.rarity}</span>}
{card.rarity && (
<span className="capitalize">{card.rarity}</span>
)}
</div>
</div>
</div>

View file

@ -5,6 +5,17 @@ import Layout from '../components/Layout';
import { Modal, Input, Button } from '../components/ui';
import { useAuth } from '../lib/use-auth';
/* 2026-06-04 design-sweep pass: the Tailwind token classes that this
page relied on (bg-bg-secondary, text-text-primary, border-border,
bg-accent-ember, hover:bg-accent-ember-dark, focus:ring-accent-ember)
are NOT defined in tailwind.config.js they produced zero CSS,
leaving the page visually unstyled (transparent backgrounds, no
borders, no hover states). Sweep replaces those with inline
style={{ ... CSS vars ... }} and the Button / SearchBar primitives
so the page actually renders with the Liquid Glass + corner-border
design system. `rounded-lg` `rounded-xl` across the board to match
the system standard. */
export default function Decks() {
const { user } = useAuth();
const router = useRouter();
@ -16,7 +27,7 @@ export default function Decks() {
name: '',
description: '',
format: 'Commander',
is_public: false
is_public: false,
});
const fetchDecks = async () => {
@ -24,8 +35,8 @@ export default function Decks() {
const token = localStorage.getItem('auth_token');
const response = await fetch('/api/decks', {
headers: {
'Authorization': `Bearer ${token}`
}
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
@ -39,7 +50,7 @@ export default function Decks() {
} finally {
setLoading(false);
}
}
};
useEffect(() => {
if (user) {
@ -48,20 +59,17 @@ export default function Decks() {
}
}, [user]);
;
const handleCreateDeck = async (e) => {
e.preventDefault();
try {
const token = localStorage.getItem('auth_token');
const response = await fetch('/api/decks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(newDeck)
body: JSON.stringify(newDeck),
});
if (response.ok) {
@ -69,8 +77,6 @@ export default function Decks() {
setDecks([createdDeck, ...decks]);
setShowCreateModal(false);
setNewDeck({ name: '', description: '', format: 'Commander', is_public: false });
// Navigate to deck builder for the new deck
router.push(`/deck-builder?deck=${createdDeck.id}`);
} else {
console.error('Failed to create deck');
@ -82,21 +88,20 @@ export default function Decks() {
const handleEditDeck = async (e) => {
e.preventDefault();
try {
const token = localStorage.getItem('auth_token');
const response = await fetch(`/api/decks/${editingDeck.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(editingDeck)
body: JSON.stringify(editingDeck),
});
if (response.ok) {
const updatedDeck = await response.json();
setDecks(decks.map(deck => deck.id === updatedDeck.id ? updatedDeck : deck));
setDecks(decks.map((deck) => (deck.id === updatedDeck.id ? updatedDeck : deck)));
setEditingDeck(null);
} else {
console.error('Failed to update deck');
@ -110,18 +115,15 @@ export default function Decks() {
if (!confirm('Are you sure you want to delete this deck? This action cannot be undone.')) {
return;
}
try {
const token = localStorage.getItem('auth_token');
const response = await fetch(`/api/decks/${deckId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
headers: { Authorization: `Bearer ${token}` },
});
if (response.ok) {
setDecks(decks.filter(deck => deck.id !== deckId));
setDecks(decks.filter((deck) => deck.id !== deckId));
} else {
console.error('Failed to delete deck');
}
@ -132,41 +134,46 @@ export default function Decks() {
const getFormatIcon = (format) => {
switch (format) {
case 'Commander':
return '⚔️';
case 'Standard':
return '🏆';
case 'Modern':
return '🔥';
case 'Legacy':
return '💎';
default:
return '🃏';
case 'Commander': return '⚔️';
case 'Standard': return '🏆';
case 'Modern': return '🔥';
case 'Legacy': return '💎';
default: return '🃏';
}
};
const getFormatColor = (format) => {
switch (format) {
case 'Commander':
return 'bg-purple-100 text-purple-800';
case 'Standard':
return 'bg-blue-100 text-blue-800';
case 'Modern':
return 'bg-red-100 text-red-800';
case 'Legacy':
return 'bg-yellow-100 text-yellow-800';
default:
return 'bg-gray-100 text-gray-800';
}
// Single neutral chip style for format badges — the prior
// getFormatColor() returned 5 stale Tailwind colour pairs
// (bg-purple-100, bg-blue-100, etc.) that don't fit the
// Deck Hearth palette and don't render in dark theme anyway.
const formatBadgeStyle = {
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-primary)',
};
// Reusable inline-style for the .card surfaces used as stat
// cards + deck cards. We can't use the .glass-panel class +
// rounded-xl directly because we want the same gradient-border
// catch-light effect that the chrome chips have, and inline
// styles for that pattern are verbose. Easiest path: opt into
// the existing .glass-panel class for the multi-layer bg, then
// override border-radius via className. (.glass-panel doesn't
// set border-radius so we control it via Tailwind.)
const chipBgStyle = {};
// textarea / select use .input-field which is the Deck-Hearth
// standard input class (rounded-2xl + ember focus ring).
const inputFieldClass = 'input-field w-full';
if (!user) {
return (
<Layout user={user}>
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<h1 className="text-2xl font-bold mb-4">Please log in to view your decks</h1>
<Link href="/login" className="text-accent-ember hover:underline">
<h1 className="text-2xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
Please log in to view your decks
</h1>
<Link href="/login" style={{ color: 'var(--accent-ember)' }} className="hover:underline">
Go to Login
</Link>
</div>
@ -179,7 +186,10 @@ export default function Decks() {
return (
<Layout user={user}>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-accent-ember"></div>
<div
className="animate-spin rounded-full h-32 w-32 border-b-2"
style={{ borderColor: 'var(--accent-ember)' }}
/>
</div>
</Layout>
);
@ -191,42 +201,43 @@ export default function Decks() {
{/* Header */}
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-3xl font-bold text-text-primary">My Decks</h1>
<p className="text-text-secondary mt-2">
<h1 className="text-3xl font-bold" style={{ color: 'var(--text-primary)' }}>
My Decks
</h1>
<p className="mt-2" style={{ color: 'var(--text-secondary)' }}>
Build and manage your MTG decks
</p>
</div>
<button
onClick={() => setShowCreateModal(true)}
className="bg-accent-ember text-white px-6 py-3 rounded-lg hover:bg-accent-ember-dark transition-colors"
>
<Button variant="primary" onClick={() => setShowCreateModal(true)}>
Create New Deck
</button>
</Button>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div className="bg-bg-secondary rounded-lg p-6">
<div className="text-2xl font-bold text-text-primary">{decks.length}</div>
<div className="text-text-secondary">Total Decks</div>
</div>
<div className="bg-bg-secondary rounded-lg p-6">
<div className="text-2xl font-bold text-text-primary">
{decks.filter(d => d.format === 'Commander').length}
<div className="glass-panel rounded-2xl p-6" style={chipBgStyle}>
<div className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
{decks.length}
</div>
<div className="text-text-secondary">Commander</div>
<div style={{ color: 'var(--text-secondary)' }}>Total Decks</div>
</div>
<div className="bg-bg-secondary rounded-lg p-6">
<div className="text-2xl font-bold text-text-primary">
{decks.filter(d => d.is_public).length}
<div className="glass-panel rounded-2xl p-6">
<div className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
{decks.filter((d) => d.format === 'Commander').length}
</div>
<div className="text-text-secondary">Public</div>
<div style={{ color: 'var(--text-secondary)' }}>Commander</div>
</div>
<div className="bg-bg-secondary rounded-lg p-6">
<div className="text-2xl font-bold text-text-primary">
<div className="glass-panel rounded-2xl p-6">
<div className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
{decks.filter((d) => d.is_public).length}
</div>
<div style={{ color: 'var(--text-secondary)' }}>Public</div>
</div>
<div className="glass-panel rounded-2xl p-6">
<div className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
{decks.reduce((sum, deck) => sum + (deck.card_count || 0), 0)}
</div>
<div className="text-text-secondary">Total Cards</div>
<div style={{ color: 'var(--text-secondary)' }}>Total Cards</div>
</div>
</div>
@ -234,65 +245,83 @@ export default function Decks() {
{decks.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">No decks yet</h3>
<p className="text-text-secondary mb-6">Create your first deck to get started</p>
<button
onClick={() => setShowCreateModal(true)}
className="bg-accent-ember text-white px-6 py-3 rounded-lg hover:bg-accent-ember-dark transition-colors"
>
<h3 className="text-xl font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
No decks yet
</h3>
<p className="mb-6" style={{ color: 'var(--text-secondary)' }}>
Create your first deck to get started
</p>
<Button variant="primary" onClick={() => setShowCreateModal(true)}>
Create Your First Deck
</button>
</Button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{decks.map((deck) => (
<div key={deck.id} className="bg-bg-secondary rounded-lg p-6 hover:shadow-lg transition-shadow">
<div key={deck.id} className="glass-panel rounded-2xl p-6 transition-shadow">
<div className="flex justify-between items-start mb-4">
<div className="flex items-center space-x-2">
<span className="text-2xl">{getFormatIcon(deck.format)}</span>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getFormatColor(deck.format)}`}>
<span
className="px-2 py-1 rounded-full text-xs font-medium"
style={formatBadgeStyle}
>
{deck.format}
</span>
</div>
<div className="flex space-x-2">
<button
onClick={() => setEditingDeck({...deck})}
className="text-text-secondary hover:text-accent-ember transition-colors"
onClick={() => setEditingDeck({ ...deck })}
className="transition-colors"
style={{ color: 'var(--text-secondary)' }}
aria-label="Edit deck"
>
</button>
<button
onClick={() => handleDeleteDeck(deck.id)}
className="text-text-secondary hover:text-red-500 transition-colors"
className="transition-colors"
style={{ color: 'var(--text-secondary)' }}
aria-label="Delete deck"
>
🗑
</button>
</div>
</div>
<h3 className="text-xl font-bold text-text-primary mb-2">{deck.name}</h3>
<h3 className="text-xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
{deck.name}
</h3>
{deck.description && (
<p className="text-text-secondary text-sm mb-4 line-clamp-2">{deck.description}</p>
<p
className="text-sm mb-4 line-clamp-2"
style={{ color: 'var(--text-secondary)' }}
>
{deck.description}
</p>
)}
<div className="flex justify-between items-center text-sm text-text-secondary mb-4">
<div
className="flex justify-between items-center text-sm mb-4"
style={{ color: 'var(--text-secondary)' }}
>
<span>{deck.card_count || 0} cards</span>
{deck.is_public && <span className="text-green-600">Public</span>}
{deck.is_public && (
<span style={{ color: 'var(--accent-ember)' }}>Public</span>
)}
</div>
<div className="flex space-x-2">
<Link
href={`/deck-builder?deck=${deck.id}`}
className="flex-1 bg-accent-ember text-white text-center py-2 rounded-lg hover:bg-accent-ember-dark transition-colors"
>
Edit Deck
<Link href={`/deck-builder?deck=${deck.id}`} className="flex-1">
<Button variant="primary" size="md" className="w-full">
Edit Deck
</Button>
</Link>
<Link
href={`/deck/${deck.id}`}
className="flex-1 bg-bg-tertiary text-text-primary text-center py-2 rounded-lg hover:bg-bg-primary transition-colors"
>
View
<Link href={`/deck/${deck.id}`} className="flex-1">
<Button variant="secondary" size="md" className="w-full">
View
</Button>
</Link>
</div>
</div>
@ -326,7 +355,7 @@ export default function Decks() {
id="create-deck-format"
value={newDeck.format}
onChange={(e) => setNewDeck({ ...newDeck, format: e.target.value })}
className="w-full px-3 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-ember bg-bg-secondary text-text-primary"
className={inputFieldClass}
>
<option value="Commander">Commander</option>
<option value="Standard">Standard</option>
@ -346,7 +375,7 @@ export default function Decks() {
id="create-deck-description"
value={newDeck.description}
onChange={(e) => setNewDeck({ ...newDeck, description: e.target.value })}
className="w-full px-3 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-ember bg-bg-secondary text-text-primary"
className={inputFieldClass}
rows="3"
placeholder="Describe your deck strategy..."
/>
@ -391,9 +420,7 @@ export default function Decks() {
label="Deck Name *"
required
value={editingDeck.name}
onChange={(e) =>
setEditingDeck({ ...editingDeck, name: e.target.value })
}
onChange={(e) => setEditingDeck({ ...editingDeck, name: e.target.value })}
/>
<div>
<label
@ -406,10 +433,8 @@ export default function Decks() {
<select
id="edit-deck-format"
value={editingDeck.format}
onChange={(e) =>
setEditingDeck({ ...editingDeck, format: e.target.value })
}
className="w-full px-3 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-ember bg-bg-secondary text-text-primary"
onChange={(e) => setEditingDeck({ ...editingDeck, format: e.target.value })}
className={inputFieldClass}
>
<option value="Commander">Commander</option>
<option value="Standard">Standard</option>
@ -428,10 +453,8 @@ export default function Decks() {
<textarea
id="edit-deck-description"
value={editingDeck.description || ''}
onChange={(e) =>
setEditingDeck({ ...editingDeck, description: e.target.value })
}
className="w-full px-3 py-2 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-ember bg-bg-secondary text-text-primary"
onChange={(e) => setEditingDeck({ ...editingDeck, description: e.target.value })}
className={inputFieldClass}
rows="3"
/>
</div>
@ -439,9 +462,7 @@ export default function Decks() {
<input
type="checkbox"
checked={editingDeck.is_public}
onChange={(e) =>
setEditingDeck({ ...editingDeck, is_public: e.target.checked })
}
onChange={(e) => setEditingDeck({ ...editingDeck, is_public: e.target.checked })}
className="mr-2"
style={{ accentColor: 'var(--accent-ember)' }}
/>

View file

@ -7,7 +7,7 @@ import BulkSelectionToolbar from '../components/BulkSelectionToolbar';
import CollectionSelectionModal from '../components/CollectionSelectionModal';
import { ManaCost, ColorFilterSymbol } from '../components/ManaSymbols';
import ManaSymbolSettings from '../components/ManaSymbolSettings';
import { Button } from '../components/ui';
import { Button, SearchBar } from '../components/ui';
import { useAuth } from '../lib/use-auth';
import { VOCAB } from '../lib/collection-vocabulary.js';
@ -310,52 +310,40 @@ export default function MyCards() {
</div>
{/* Filters */}
<div className="p-4 sm:p-6" style={{ backgroundColor: 'var(--bg-primary)' }}>
<div className="flex flex-wrap gap-4 mb-6">
<input
type="text"
placeholder="Search your cards..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="flex-1 min-w-[200px] px-4 py-2 rounded-xl border"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)',
color: 'var(--text-primary)'
}}
/>
<div className="p-4 sm:p-6">
<div className="glass-panel rounded-2xl p-4 mb-6">
<div className="flex flex-wrap gap-4">
<div className="flex-1 min-w-[200px]">
<SearchBar
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onClear={() => setSearchQuery('')}
placeholder="Search your cards…"
/>
</div>
<select
value={selectedTCG}
onChange={(e) => setSelectedTCG(e.target.value)}
className="px-4 py-2 rounded-xl border"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)',
color: 'var(--text-primary)'
}}
>
<option value="all">All Games</option>
{filters.games.map(game => (
<option key={game} value={game}>{game}</option>
))}
</select>
<select
value={selectedTCG}
onChange={(e) => setSelectedTCG(e.target.value)}
className="input-field"
>
<option value="all">All Games</option>
{filters.games.map(game => (
<option key={game} value={game}>{game}</option>
))}
</select>
<select
value={selectedRarity}
onChange={(e) => setSelectedRarity(e.target.value)}
className="px-4 py-2 rounded-xl border"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)',
color: 'var(--text-primary)'
}}
>
<option value="all">All Rarities</option>
{filters.rarities.map(rarity => (
<option key={rarity} value={rarity}>{rarity}</option>
))}
</select>
<select
value={selectedRarity}
onChange={(e) => setSelectedRarity(e.target.value)}
className="input-field"
>
<option value="all">All Rarities</option>
{filters.rarities.map(rarity => (
<option key={rarity} value={rarity}>{rarity}</option>
))}
</select>
</div>
</div>
{/* Results Summary */}

View file

@ -275,11 +275,15 @@ export default function Profile() {
{/* Message */}
{message.text && (
<div className={`mb-6 p-4 rounded-xl ${
message.type === 'success'
? 'bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200 border border-green-200 dark:border-green-800'
: 'bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200 border border-red-200 dark:border-red-800'
}`}>
<div
className="mb-6 p-4 rounded-xl glass-panel"
style={{
color:
message.type === 'success' ? 'var(--accent-flame)' : '#dc2626',
borderColor:
message.type === 'success' ? 'var(--accent-flame)' : '#dc2626',
}}
>
{message.text}
</div>
)}

View file

@ -251,11 +251,15 @@ export default function Settings() {
{/* Message */}
{message.text && (
<div className={`mb-6 p-4 rounded-xl ${
message.type === 'success'
? 'bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200 border border-green-200 dark:border-green-800'
: 'bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200 border border-red-200 dark:border-red-800'
}`}>
<div
className="mb-6 p-4 rounded-xl glass-panel"
style={{
color:
message.type === 'success' ? 'var(--accent-flame)' : '#dc2626',
borderColor:
message.type === 'success' ? 'var(--accent-flame)' : '#dc2626',
}}
>
{message.text}
</div>
)}
@ -269,13 +273,9 @@ export default function Settings() {
<button
key={section.id}
onClick={() => setActiveSection(section.id)}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl font-medium transition-all duration-200 text-left ${
activeSection === section.id ? 'shadow-lg' : 'hover:shadow-md'
className={`nav-item w-full flex items-center gap-3 px-4 py-3 font-medium text-left ${
activeSection === section.id ? 'nav-item-active' : 'nav-item-hover'
}`}
style={{
backgroundColor: activeSection === section.id ? 'var(--accent-ember)' : 'transparent',
color: activeSection === section.id ? 'white' : 'var(--text-primary)'
}}
>
<span>{section.icon}</span>
{section.name}