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

Comprehensive design sweep across the rest of the app following the
shipped Liquid Glass + corner-border-light system (#116).

## Three classes of finding

### 1. Broken Tailwind token classes (HIGH — pages were unstyled)

The decks / deck-builder / deck-detail cluster relied on Tailwind
classes that don't exist in `tailwind.config.js` (no `bg-bg-*`,
`text-text-*`, `border-border`, `bg-accent-ember`,
`focus:ring-accent-ember`, `hover:bg-accent-ember-dark`). Those classes
produced ZERO CSS — backgrounds were transparent, borders invisible,
hover states absent.

Rewrote with inline `style={{ ... CSS vars ... }}` + the `<Button>` /
`<SearchBar>` primitives + `glass-panel` surfaces:

- `pages/decks.js` (full page)
- `pages/deck/[id].js` (header, stats sidebar, group-by controls,
  card list)
- `pages/deck-builder.js` (loading spinner)
- `components/DeckBuilderView.js` (toolbar + main panel)
- `components/DeckBuilderCardBrowser.js` (full rewrite; integrated
  `<SearchBar>` for the card-picker input)
- `components/DeckBuilderDeckList.js` (full rewrite)
- `components/DeckBuilderStatsBar.js`
- `components/ManaSymbolSettings.js`
- `components/ManaSymbols.js` (single `text-text-secondary`)
- `pages/admin/card-editor.js` cluster was already clean

### 2. Duplicative / stale page searches

Replaced raw `<input>` search controls with the `<SearchBar>` primitive
(adds clear button, ember focus ring, system-consistent rounded
corners). Kept page-specific filter searches (they filter the visible
list — distinct from the global TopSearchBar command palette):

- `pages/my-cards.js`
- `pages/community/collections.js`
- `components/CardsPageView.js`
- `components/CollectionPageView.js`
- `components/DeckBuilderCardBrowser.js`

`pages/my-cards.js` filter wrapper also lifted into a `glass-panel`
chip instead of a solid `var(--bg-primary)` band.

### 3. Square corners + stale palette in shared views

- `components/CollectionPageView.js`: 10 action buttons (`rounded-lg`
  + `hover:bg-gray-50`) → `rounded-xl` + `nav-item-hover`; 4 filter
  selects (`focus:ring-purple-500 rounded-lg`) → `.input-field`;
  view-mode toggle (`bg-white text-gray-900` — invisible in dark mode)
  → tokenised; SYSTEM badge gradient (`from-blue-500 to-purple-600`)
  → ember↔flame; tooltip (`bg-gray-900`) → `glass-panel-strong`;
  search-results dropdown (`bg-white border-gray-200` — invisible in
  dark mode) → `glass-panel-strong`; Activity / game-count /
  TCG-game badges palette-aligned.
- `components/CardsPageView.js`: "Load More Cards" button
  (`bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg`) →
  `<Button variant="primary" size="lg">`.
- `components/CollectionsPageView.js`: matching SYSTEM badge +
  tooltip cleanup.
- `components/ShareModal.js`: user-search dropdown
  (`border-gray-200 hover:bg-gray-50`) and email-invite card moved
  onto `glass-panel` + `nav-item-hover`; social-share buttons
  `rounded-lg hover:bg-gray-50` → `rounded-xl nav-item-hover`.
- `components/Layout.js`: profile-menu dropdown row
  (`hover:bg-gray-50 dark:hover:bg-gray-700`) → `nav-item-hover`.
- `components/CardItem.js`: bulk-select checkbox
  `focus:ring-purple-500` → ember.

### 4. `dark:` modifier classes (broken with `[data-theme]` theming)

This app uses `[data-theme="dark"]` CSS selector theming, not
Tailwind's `class` strategy, so `dark:bg-green-900/20` etc. produced
no CSS in dark mode. Affected alerts on `pages/settings.js` and
`pages/profile.js` — replaced with `glass-panel` + semantic border
colour (flame for success, #dc2626 for error).

`pages/settings.js` sidebar nav also moved off its hardcoded full-ember
fill onto the system `nav-item` / `nav-item-active` / `nav-item-hover`
pattern for consistency with the global sidebar.

## Verification

- `npm run build` — green (Next 16 + Turbopack)
- `npm run lint` — 0 errors, 1 unrelated pre-existing warning
- `npm run test:run` — 113/113 pass (no test changes needed)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Randall Stillwell 2026-06-04 12:55:29 -05:00
parent c57b5d0406
commit 1769d4576a
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,
@ -97,9 +98,9 @@ export default function CollectionPageView(props) {
{/* Edit and Delete buttons - only show for owner and non-system collections */}
{collection.userRole === 'owner' && !collection.isSystemCollection && (
<>
<button
<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">
@ -107,9 +108,9 @@ export default function CollectionPageView(props) {
</svg>
<span>Edit</span>
</button>
<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">
@ -119,15 +120,18 @@ export default function CollectionPageView(props) {
</button>
</>
)}
<button
<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>
@ -163,11 +182,15 @@ export default function CollectionPageView(props) {
{/* TCG Tags */}
<div className="flex items-center space-x-2 mb-4">
{Object.entries(gameStats).map(([game, count]) =>
{Object.entries(gameStats).map(([game, count]) =>
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" />
@ -224,9 +255,9 @@ export default function CollectionPageView(props) {
<span>Favorite</span>
</button>
<button
<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>
@ -278,12 +314,11 @@ export default function CollectionPageView(props) {
<option>Mythic</option>
<option>Legendary</option>
</select>
<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>
@ -292,23 +327,21 @@ export default function CollectionPageView(props) {
<option>Artifact</option>
<option>Enchantment</option>
</select>
<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>
<option>Group by Type</option>
</select>
<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,27 +374,39 @@ 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" />
</svg>
</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>
}
>
Add Cards
</Button>
<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="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
<span>Add Cards</span>
</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)' }}>
<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 ${
@ -52,4 +61,4 @@ export default function ManaSymbolSettings({ onSettingsChange }) {
</button>
</div>
);
}
}

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"
style={{
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-secondary)'
className="px-2 py-1 rounded-xl text-xs font-medium"
style={{
backgroundColor: 'var(--bg-tertiary)',
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"
style={{
backgroundColor: 'var(--bg-tertiary)',
color: 'var(--text-secondary)'
className="px-2 py-1 rounded-xl text-xs font-medium"
style={{
backgroundColor: 'var(--bg-tertiary)',
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)'
}}
/>
<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>
<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={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={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="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}