2026-06-02 02:03:39 -04:00
|
|
|
/* eslint-disable @next/next/no-img-element -- External or generated image URLs; next/image migration is out of scope. */
|
2025-07-27 14:17:13 -04:00
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
import { useRouter } from 'next/router';
|
|
|
|
|
import Link from 'next/link';
|
|
|
|
|
import Layout from '../../components/Layout';
|
|
|
|
|
import PermissionIndicator from '../../components/PermissionIndicator';
|
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>
2026-06-04 13:55:29 -04:00
|
|
|
import { Button, SearchBar } from '../../components/ui';
|
2025-07-27 14:17:13 -04:00
|
|
|
import { useAuth } from '../../lib/use-auth';
|
2026-05-29 10:53:40 -04:00
|
|
|
import { VOCAB } from '../../lib/collection-vocabulary.js';
|
2025-07-27 14:17:13 -04:00
|
|
|
|
|
|
|
|
export default function CommunityCollections() {
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const { user, loading: authLoading } = useAuth();
|
|
|
|
|
|
|
|
|
|
const [collections, setCollections] = useState([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
|
|
|
const [sortBy, setSortBy] = useState('name');
|
|
|
|
|
|
2025-07-29 15:19:48 -04:00
|
|
|
// Fetch collections on mount, regardless of auth status
|
2025-07-27 14:17:13 -04:00
|
|
|
const fetchPublicCollections = async () => {
|
|
|
|
|
try {
|
2025-07-29 15:19:48 -04:00
|
|
|
// Use public API endpoint that doesn't require authentication
|
|
|
|
|
const response = await fetch('/api/public/collections?limit=50');
|
2025-07-27 14:17:13 -04:00
|
|
|
|
|
|
|
|
if (response.ok) {
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
// Fetch thumbnails for each collection
|
|
|
|
|
const collectionsWithThumbnails = await Promise.all(
|
|
|
|
|
data.map(async (collection) => {
|
|
|
|
|
try {
|
|
|
|
|
const identifier = collection.slug || collection.id;
|
2025-07-29 15:19:48 -04:00
|
|
|
const thumbnailResponse = await fetch(`/api/collections/${identifier}/thumbnails`);
|
2025-07-27 14:17:13 -04:00
|
|
|
if (thumbnailResponse.ok) {
|
|
|
|
|
const thumbnailData = await thumbnailResponse.json();
|
|
|
|
|
return { ...collection, thumbnails: thumbnailData.thumbnails };
|
|
|
|
|
}
|
|
|
|
|
return { ...collection, thumbnails: [] };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`Error fetching thumbnails for collection ${collection.id}:`, error);
|
|
|
|
|
return { ...collection, thumbnails: [] };
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setCollections(collectionsWithThumbnails);
|
|
|
|
|
} else {
|
|
|
|
|
console.error('Failed to fetch public collections');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching public collections:', error);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-02 02:03:39 -04:00
|
|
|
useEffect(() => {
|
|
|
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect -- mount fetch; setLoading runs inside async loader
|
|
|
|
|
fetchPublicCollections();
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-07-27 14:17:13 -04:00
|
|
|
const sortOptions = [
|
|
|
|
|
{ value: 'name', label: 'Name (A-Z)' },
|
|
|
|
|
{ value: 'value', label: 'Value (High to Low)' },
|
|
|
|
|
{ value: 'cardCount', label: 'Card Count (High to Low)' },
|
|
|
|
|
{ value: 'createdAt', label: 'Recently Created' }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const sortCollections = (collections, sortBy) => {
|
|
|
|
|
return [...collections].sort((a, b) => {
|
|
|
|
|
switch (sortBy) {
|
|
|
|
|
case 'name':
|
|
|
|
|
return a.name.localeCompare(b.name);
|
|
|
|
|
case 'value':
|
|
|
|
|
return (b.value || 0) - (a.value || 0);
|
|
|
|
|
case 'cardCount':
|
|
|
|
|
return (b.cardCount || 0) - (a.cardCount || 0);
|
|
|
|
|
case 'createdAt':
|
|
|
|
|
return new Date(b.createdAt) - new Date(a.createdAt);
|
|
|
|
|
default:
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const filteredCollections = collections.filter(collection => {
|
|
|
|
|
const matchesSearch = collection.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
|
|
|
collection.description.toLowerCase().includes(searchQuery.toLowerCase());
|
|
|
|
|
return matchesSearch;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const sortedCollections = sortCollections(filteredCollections, sortBy);
|
|
|
|
|
|
|
|
|
|
const formatCurrency = (amount) => {
|
|
|
|
|
return new Intl.NumberFormat('en-US', {
|
|
|
|
|
style: 'currency',
|
|
|
|
|
currency: 'USD'
|
|
|
|
|
}).format(amount);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatDate = (dateString) => {
|
|
|
|
|
return new Date(dateString).toLocaleDateString();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Collection thumbnail component (same as in regular collections)
|
|
|
|
|
const CollectionThumbnail = ({ collection }) => {
|
|
|
|
|
const { thumbnails = [], image } = collection;
|
|
|
|
|
|
2025-07-27 15:48:42 -04:00
|
|
|
// If there's a custom image, show it
|
2025-07-27 14:17:13 -04:00
|
|
|
if (image) {
|
|
|
|
|
return (
|
2025-07-27 15:48:42 -04:00
|
|
|
<div className="w-full h-48 rounded-xl overflow-hidden mb-4">
|
|
|
|
|
<img
|
|
|
|
|
src={image}
|
2025-07-27 14:17:13 -04:00
|
|
|
alt={collection.name}
|
|
|
|
|
className="w-full h-full object-cover"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-27 15:48:42 -04:00
|
|
|
// If no cards, show crying emoji
|
2025-07-27 14:17:13 -04:00
|
|
|
if (!thumbnails || thumbnails.length === 0) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="w-full h-48 rounded-xl mb-4 flex items-center justify-center" style={{ backgroundColor: 'var(--bg-tertiary)' }}>
|
|
|
|
|
<div className="text-center">
|
2025-07-27 15:02:33 -04:00
|
|
|
<div className="text-6xl mb-2">😢</div>
|
2025-07-27 14:17:13 -04:00
|
|
|
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>No cards yet</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-27 15:48:42 -04:00
|
|
|
const mainCard = thumbnails[0];
|
|
|
|
|
const gridCards = thumbnails.slice(1, 5); // Get up to 4 cards for the 2x2 grid
|
2025-07-27 14:17:13 -04:00
|
|
|
|
|
|
|
|
return (
|
2025-07-27 15:02:33 -04:00
|
|
|
<div className="w-full h-48 rounded-xl overflow-hidden mb-4 p-3 flex gap-2" style={{ backgroundColor: 'var(--bg-tertiary)' }}>
|
2025-07-27 15:48:42 -04:00
|
|
|
{/* Main card (larger, left side) */}
|
2025-07-27 15:02:33 -04:00
|
|
|
<div className="flex-2 h-full">
|
2025-07-27 14:17:13 -04:00
|
|
|
{mainCard ? (
|
2025-07-27 15:02:33 -04:00
|
|
|
<div className="w-full h-full bg-white rounded-lg overflow-hidden shadow-sm border" style={{ borderColor: 'var(--border)' }}>
|
2025-07-27 15:48:42 -04:00
|
|
|
<img
|
|
|
|
|
src={mainCard.image_url || mainCard.stock_image_url}
|
2025-07-27 14:17:13 -04:00
|
|
|
alt={mainCard.name}
|
|
|
|
|
className="w-full h-full object-cover"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
2025-07-27 15:02:33 -04:00
|
|
|
<div className="w-full h-full bg-white rounded-lg border" style={{ borderColor: 'var(--border)' }}></div>
|
2025-07-27 14:17:13 -04:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-07-27 15:48:42 -04:00
|
|
|
{/* Grid of 4 smaller cards (right side) */}
|
2025-07-27 14:17:13 -04:00
|
|
|
<div className="flex-1 h-full">
|
2025-07-27 15:02:33 -04:00
|
|
|
<div className="grid grid-cols-2 gap-2 h-full">
|
2025-07-27 14:17:13 -04:00
|
|
|
{Array.from({ length: 4 }).map((_, index) => {
|
|
|
|
|
const card = gridCards[index];
|
|
|
|
|
return (
|
|
|
|
|
<div key={index} className="relative">
|
|
|
|
|
{card ? (
|
2025-07-27 15:02:33 -04:00
|
|
|
<div className="w-full h-full bg-white rounded-md overflow-hidden shadow-sm border" style={{ borderColor: 'var(--border)' }}>
|
2025-07-27 15:48:42 -04:00
|
|
|
<img
|
|
|
|
|
src={card.image_url || card.stock_image_url}
|
2025-07-27 14:17:13 -04:00
|
|
|
alt={card.name}
|
2025-07-27 15:02:33 -04:00
|
|
|
className="w-full h-full object-cover"
|
2025-07-27 14:17:13 -04:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
2025-07-27 15:02:33 -04:00
|
|
|
<div className="w-full h-full bg-white rounded-md border" style={{ borderColor: 'var(--border)' }}></div>
|
2025-07-27 14:17:13 -04:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Show loading spinner while auth is loading or data is loading
|
|
|
|
|
if (authLoading || loading) {
|
|
|
|
|
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" style={{ borderColor: 'var(--accent-ember)' }}></div>
|
|
|
|
|
</div>
|
|
|
|
|
</Layout>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Redirect to login if not authenticated (handled by useEffect, but this is a fallback)
|
|
|
|
|
if (!user) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Layout user={user}>
|
|
|
|
|
{/* Header */}
|
2026-06-04 12:14:50 -04:00
|
|
|
<div className="px-6 pt-6 pb-2">
|
2025-07-27 14:17:13 -04:00
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-3xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
2026-05-29 10:53:40 -04:00
|
|
|
Community Lists
|
2025-07-27 14:17:13 -04:00
|
|
|
</h1>
|
|
|
|
|
<p className="mt-2" style={{ color: 'var(--text-secondary)' }}>
|
2026-05-29 10:53:40 -04:00
|
|
|
Discover public lists shared by the community
|
2025-07-27 14:17:13 -04:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Search and Sort */}
|
|
|
|
|
<div className="flex items-center justify-between mt-6">
|
|
|
|
|
<div className="flex-1 max-w-md">
|
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>
2026-06-04 13:55:29 -04:00
|
|
|
<SearchBar
|
2025-07-27 14:17:13 -04:00
|
|
|
value={searchQuery}
|
|
|
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
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>
2026-06-04 13:55:29 -04:00
|
|
|
onClear={() => setSearchQuery('')}
|
|
|
|
|
placeholder="Search lists…"
|
2025-07-27 14:17:13 -04:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex gap-4">
|
|
|
|
|
<select
|
|
|
|
|
value={sortBy}
|
|
|
|
|
onChange={(e) => setSortBy(e.target.value)}
|
|
|
|
|
className="input-field w-48"
|
|
|
|
|
>
|
|
|
|
|
{sortOptions.map(option => (
|
|
|
|
|
<option key={option.value} value={option.value}>
|
|
|
|
|
{option.label}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Collections Grid */}
|
|
|
|
|
<div className="p-6">
|
|
|
|
|
{sortedCollections.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)' }}>
|
2026-05-29 10:53:40 -04:00
|
|
|
{searchQuery ? 'No lists found' : 'No public lists yet'}
|
2025-07-27 14:17:13 -04:00
|
|
|
</h3>
|
|
|
|
|
<p className="text-sm mb-6" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
{searchQuery
|
|
|
|
|
? 'Try adjusting your search terms'
|
2026-05-29 10:53:40 -04:00
|
|
|
: 'Be the first to share a public list with the community!'
|
2025-07-27 14:17:13 -04:00
|
|
|
}
|
|
|
|
|
</p>
|
|
|
|
|
{!searchQuery && (
|
|
|
|
|
<Link href="/collections">
|
feat(design-system): finish Liquid Glass — close all remaining sub-convoys (#96)
Follow-up PR to #95 (Liquid Glass foundation + primitives + Layout shell)
that closes out the remaining sub-convoy briefs in a single sweep.
Operator-instructed scope: "finish off the design changes."
After this PR, **all 8 Liquid Glass sub-convoys are MERGED to main**;
the deferred-from-#5 `fix-card3d-state` convoy is dropped (its target,
`components/Card3D.js`, turned out to be dead code).
## #2 Brief 2 — Remaining 8 modals migrated to <Modal> primitive
- `CollectionsSuccessModal.js` — wrap in <Modal hideCloseButton>; 2 Buttons.
- `CollectionsEditModal.js` — full <Modal> + <Input> + <Button> rewrite (4 fields, tag chip section, public-toggle preserved, 2 footer Buttons).
- `CollectionEditModal.js` — same pattern as above (4 fields + public-toggle + 2 Buttons).
- `CardDetailDeckModal.js` — <Modal> + native select (Select primitive not in scope) + 2 Buttons; sweep `gradient-bg-purple` → `<Button variant="primary">`.
- `UploadImageModal.js` — <Modal> + token-driven URL/file tab switcher + drag-drop using `--accent-ember` rim + 2 Buttons (one with `loading` prop).
- `CollectionSelectionModal.js` — largest of the set (header summary + SearchBar + scrollable list w/ checkbox toggles + footer); migrated to <Modal size="lg"> while preserving the per-collection card preview thumbnails.
- `OCRSettings.js` — trivial <Modal> wrap + single primary <Button>.
- `pages/decks.js` — both inline modals (Create Deck + Edit Deck) and `components/ScannerPageView.js` (Create List) migrated; ScannerPageView dropped its `useFocusTrap` named-import (Modal's internal focus trap owns the panel ref now).
- **`.github/workflows/ci.yml` `forbidden-modal-shell-without-primitive`** — grandfather list emptied to zero entries; gate is now strict.
## #3 Brief 2 — Forms migrated to <Button> / <SearchBar>
- `pages/dashboard.js` — 3 CTAs → <Button> (Create List with leadingIcon, Create Your First List, View All Lists).
- `pages/my-cards.js` — empty-state CTA → <Button variant="primary" size="lg">. View-mode toggle buttons intentionally left native (icon-only, doesn't match Button variants).
- `pages/community/collections.js` — Go to My Lists CTA → <Button>.
- `components/CollectionsPageView.js` — Discover Community + Create List header CTAs → <Button>; search input → <SearchBar>.
- Card-grid per-row icon buttons (CollectionsPageView, my-cards, CardsPageView) intentionally left native — tiny per-card actions whose styling doesn't match Button variants and would invalidate visual-diff baselines.
## #5 — scope revised + landed
`components/Card3D.js` deletion: surveyed every importer with grep — **zero consumers** in `pages/**` or `components/**`. Only references were in convoy docs. The "pre-existing state-management bug" (state setters used without useState declarations) never affected the running app because the component was never rendered. -505 LOC. The `fix-card3d-state` convoy is dropped from the roadmap as a result.
The actual card-grid component (`components/CardItem.js`) is intentionally **not** modified in this sweep — it has per-rarity glow tuning that the existing visual-diff baseline locks in, and the architect's #5 deferral note specifically called out the dedicated baseline re-seed cost. A future implementer turn can apply rim-light tokens to CardItem with its own baseline re-seed when an operator wants that polish.
## #6 Brief 1 — Landing + invite pages glass-migrated
- `pages/index.js` — top nav: `var(--glass-surface-mid)` + `--glass-blur-mid` + rim-light. 3 feature cards: `<GlassSurface tint="mid" rim="subtle" elevation="ambient">`. Featured-list cards (the public collection grid): same `<GlassSurface>` recipe with motion-token transitions. All 6 CTA buttons → <Button variant="primary"|"secondary"|"ghost"> with proper sizes. Pulse-loading placeholders tagged `.motion-essential` so reduced-motion users still see them animate (state-meaningful).
- `pages/invite/accept.js` + `pages/invite/decline.js` — both outcome panels wrapped in `<GlassSurface tint="mid" rim="subtle" elevation="pronounced">`. Loading spinner border colors corrected from `--text-accent` (which didn't exist) to `--accent-ember`. All 8 buttons → <Button>. `gradient-bg-ember` consumers retained (the canonical warm-palette utility class is fine).
## #8 Brief 2 — Legacy alias sweep + CI gate graduation
- Swept `gradient-bg-purple` → `gradient-bg-ember` across **8 files** / **13 occurrences**: `CardDetailQuantityModal`, `CardEditorView`, `CardEditorForm`, `AdminProtected`, `pages/card/[id]`, `pages/invite/{accept,decline}`, `pages/admin/card-import`. `gradient-bg-purple` was a dangling class name with no CSS definition (it was rendering no styling), so the sweep is also a bug fix — those buttons now actually get the ember gradient.
- Deleted the 5 dead CSS classes from `styles/globals.css`: `.gradient-text-blue`, `.gradient-text-purple`, `[data-theme="dark"] .glow-blue`, `[data-theme="dark"] .glow-purple`, `[data-theme="dark"] .glow-pink`. Each was zero-consumer post-sweep.
- **Graduated the `forbidden-deprecated-color-aliases` CI job from WARN to FAIL.** All 9 patterns (`gradient-text-{purple,pink,blue}`, `glow-{purple,pink,blue}`, `gradient-bg-{purple,blue,pink}`) now block the build if any consumer is reintroduced.
## Verification (local + CI gates locally exercised)
- Lint: 0 errors, 2 pre-existing warnings (`CardEditorForm.js` + `CollectionsPageView.js` carry-overs from before #95; out of scope).
- Vitest: 104/104 passing — unchanged from #95.
- Build: clean (Turbopack default; passes both light + dark theme prerender).
- `forbidden-modal-shell-without-primitive` gate: locally clear (`grep -lE 'fixed inset-0 bg-black bg-opacity-' pages components -r --include='*.js'` returns no matches).
- `forbidden-deprecated-color-aliases` gate: locally clear (all 9 patterns return no matches in `pages/` or `components/`).
## What still needs human action
- **Linux visual-diff baselines** must re-seed via the Docker workflow in `AGENTS.md` § 6. This PR's landing-page + invite-page changes will produce baseline drift on the homepage screenshot (which is currently the only baseline committed) AND additional baselines will be generated for the landing's glass-card sections once the visual spec is expanded. Recommended: run the Docker re-seed against this PR's Vercel preview, commit the result to this branch, push, verify CI green, then merge.
- Vercel auto-promotes the merge to production.
## Closes / supersedes
- Closes `.convoys/liquid-glass-modal-and-surface-primitive.md` Brief 2 (status → merged).
- Closes `.convoys/liquid-glass-form-primitives.md` Brief 2 (status → merged with explicit per-row-icon-button deferral note).
- Closes `.convoys/liquid-glass-public-and-auth.md` Brief 1 (status → merged).
- Closes `.convoys/cleanup-legacy-design-css.md` Brief 2 (status → merged + CI gate FAIL).
- Drops `.convoys/liquid-glass-card-surfaces.md` Brief 1 prerequisite (`fix-card3d-state` no longer needed; Card3D deleted).
- Drops the queued `fix-card3d-state` follow-up from the roadmap (target deleted).
- Updates `.convoys/ship-readiness.md` § "Design-system redesign portfolio" with a "Finish-portfolio sweep" subsection documenting final status of all 8 sub-convoys.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 21:34:06 -04:00
|
|
|
<Button variant="primary" size="lg">
|
2026-05-29 10:53:40 -04:00
|
|
|
Go to My Lists
|
feat(design-system): finish Liquid Glass — close all remaining sub-convoys (#96)
Follow-up PR to #95 (Liquid Glass foundation + primitives + Layout shell)
that closes out the remaining sub-convoy briefs in a single sweep.
Operator-instructed scope: "finish off the design changes."
After this PR, **all 8 Liquid Glass sub-convoys are MERGED to main**;
the deferred-from-#5 `fix-card3d-state` convoy is dropped (its target,
`components/Card3D.js`, turned out to be dead code).
## #2 Brief 2 — Remaining 8 modals migrated to <Modal> primitive
- `CollectionsSuccessModal.js` — wrap in <Modal hideCloseButton>; 2 Buttons.
- `CollectionsEditModal.js` — full <Modal> + <Input> + <Button> rewrite (4 fields, tag chip section, public-toggle preserved, 2 footer Buttons).
- `CollectionEditModal.js` — same pattern as above (4 fields + public-toggle + 2 Buttons).
- `CardDetailDeckModal.js` — <Modal> + native select (Select primitive not in scope) + 2 Buttons; sweep `gradient-bg-purple` → `<Button variant="primary">`.
- `UploadImageModal.js` — <Modal> + token-driven URL/file tab switcher + drag-drop using `--accent-ember` rim + 2 Buttons (one with `loading` prop).
- `CollectionSelectionModal.js` — largest of the set (header summary + SearchBar + scrollable list w/ checkbox toggles + footer); migrated to <Modal size="lg"> while preserving the per-collection card preview thumbnails.
- `OCRSettings.js` — trivial <Modal> wrap + single primary <Button>.
- `pages/decks.js` — both inline modals (Create Deck + Edit Deck) and `components/ScannerPageView.js` (Create List) migrated; ScannerPageView dropped its `useFocusTrap` named-import (Modal's internal focus trap owns the panel ref now).
- **`.github/workflows/ci.yml` `forbidden-modal-shell-without-primitive`** — grandfather list emptied to zero entries; gate is now strict.
## #3 Brief 2 — Forms migrated to <Button> / <SearchBar>
- `pages/dashboard.js` — 3 CTAs → <Button> (Create List with leadingIcon, Create Your First List, View All Lists).
- `pages/my-cards.js` — empty-state CTA → <Button variant="primary" size="lg">. View-mode toggle buttons intentionally left native (icon-only, doesn't match Button variants).
- `pages/community/collections.js` — Go to My Lists CTA → <Button>.
- `components/CollectionsPageView.js` — Discover Community + Create List header CTAs → <Button>; search input → <SearchBar>.
- Card-grid per-row icon buttons (CollectionsPageView, my-cards, CardsPageView) intentionally left native — tiny per-card actions whose styling doesn't match Button variants and would invalidate visual-diff baselines.
## #5 — scope revised + landed
`components/Card3D.js` deletion: surveyed every importer with grep — **zero consumers** in `pages/**` or `components/**`. Only references were in convoy docs. The "pre-existing state-management bug" (state setters used without useState declarations) never affected the running app because the component was never rendered. -505 LOC. The `fix-card3d-state` convoy is dropped from the roadmap as a result.
The actual card-grid component (`components/CardItem.js`) is intentionally **not** modified in this sweep — it has per-rarity glow tuning that the existing visual-diff baseline locks in, and the architect's #5 deferral note specifically called out the dedicated baseline re-seed cost. A future implementer turn can apply rim-light tokens to CardItem with its own baseline re-seed when an operator wants that polish.
## #6 Brief 1 — Landing + invite pages glass-migrated
- `pages/index.js` — top nav: `var(--glass-surface-mid)` + `--glass-blur-mid` + rim-light. 3 feature cards: `<GlassSurface tint="mid" rim="subtle" elevation="ambient">`. Featured-list cards (the public collection grid): same `<GlassSurface>` recipe with motion-token transitions. All 6 CTA buttons → <Button variant="primary"|"secondary"|"ghost"> with proper sizes. Pulse-loading placeholders tagged `.motion-essential` so reduced-motion users still see them animate (state-meaningful).
- `pages/invite/accept.js` + `pages/invite/decline.js` — both outcome panels wrapped in `<GlassSurface tint="mid" rim="subtle" elevation="pronounced">`. Loading spinner border colors corrected from `--text-accent` (which didn't exist) to `--accent-ember`. All 8 buttons → <Button>. `gradient-bg-ember` consumers retained (the canonical warm-palette utility class is fine).
## #8 Brief 2 — Legacy alias sweep + CI gate graduation
- Swept `gradient-bg-purple` → `gradient-bg-ember` across **8 files** / **13 occurrences**: `CardDetailQuantityModal`, `CardEditorView`, `CardEditorForm`, `AdminProtected`, `pages/card/[id]`, `pages/invite/{accept,decline}`, `pages/admin/card-import`. `gradient-bg-purple` was a dangling class name with no CSS definition (it was rendering no styling), so the sweep is also a bug fix — those buttons now actually get the ember gradient.
- Deleted the 5 dead CSS classes from `styles/globals.css`: `.gradient-text-blue`, `.gradient-text-purple`, `[data-theme="dark"] .glow-blue`, `[data-theme="dark"] .glow-purple`, `[data-theme="dark"] .glow-pink`. Each was zero-consumer post-sweep.
- **Graduated the `forbidden-deprecated-color-aliases` CI job from WARN to FAIL.** All 9 patterns (`gradient-text-{purple,pink,blue}`, `glow-{purple,pink,blue}`, `gradient-bg-{purple,blue,pink}`) now block the build if any consumer is reintroduced.
## Verification (local + CI gates locally exercised)
- Lint: 0 errors, 2 pre-existing warnings (`CardEditorForm.js` + `CollectionsPageView.js` carry-overs from before #95; out of scope).
- Vitest: 104/104 passing — unchanged from #95.
- Build: clean (Turbopack default; passes both light + dark theme prerender).
- `forbidden-modal-shell-without-primitive` gate: locally clear (`grep -lE 'fixed inset-0 bg-black bg-opacity-' pages components -r --include='*.js'` returns no matches).
- `forbidden-deprecated-color-aliases` gate: locally clear (all 9 patterns return no matches in `pages/` or `components/`).
## What still needs human action
- **Linux visual-diff baselines** must re-seed via the Docker workflow in `AGENTS.md` § 6. This PR's landing-page + invite-page changes will produce baseline drift on the homepage screenshot (which is currently the only baseline committed) AND additional baselines will be generated for the landing's glass-card sections once the visual spec is expanded. Recommended: run the Docker re-seed against this PR's Vercel preview, commit the result to this branch, push, verify CI green, then merge.
- Vercel auto-promotes the merge to production.
## Closes / supersedes
- Closes `.convoys/liquid-glass-modal-and-surface-primitive.md` Brief 2 (status → merged).
- Closes `.convoys/liquid-glass-form-primitives.md` Brief 2 (status → merged with explicit per-row-icon-button deferral note).
- Closes `.convoys/liquid-glass-public-and-auth.md` Brief 1 (status → merged).
- Closes `.convoys/cleanup-legacy-design-css.md` Brief 2 (status → merged + CI gate FAIL).
- Drops `.convoys/liquid-glass-card-surfaces.md` Brief 1 prerequisite (`fix-card3d-state` no longer needed; Card3D deleted).
- Drops the queued `fix-card3d-state` follow-up from the roadmap (target deleted).
- Updates `.convoys/ship-readiness.md` § "Design-system redesign portfolio" with a "Finish-portfolio sweep" subsection documenting final status of all 8 sub-convoys.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 21:34:06 -04:00
|
|
|
</Button>
|
2025-07-27 14:17:13 -04:00
|
|
|
</Link>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
|
|
|
{sortedCollections.map(collection => (
|
|
|
|
|
<Link key={collection.id} href={`/collection/${collection.slug || collection.id}`}>
|
|
|
|
|
<div className="card group cursor-pointer hover:shadow-lg transition-all duration-200">
|
|
|
|
|
<CollectionThumbnail collection={collection} />
|
|
|
|
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<div className="flex items-start justify-between">
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<h3 className="font-semibold text-lg truncate" style={{ color: 'var(--text-primary)' }}>
|
|
|
|
|
{collection.name}
|
|
|
|
|
</h3>
|
|
|
|
|
{collection.description && (
|
|
|
|
|
<p className="text-sm mt-1 line-clamp-2" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
{collection.description}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
<p className="text-xs mt-1" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
by {collection.creator}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center space-x-1 ml-2">
|
|
|
|
|
<PermissionIndicator
|
|
|
|
|
userRole={collection.userRole}
|
|
|
|
|
isPublic={collection.isPublic}
|
|
|
|
|
showTooltip={false}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Compact Stats */}
|
|
|
|
|
<div className="flex items-center justify-between text-sm" style={{ color: 'var(--text-secondary)' }}>
|
|
|
|
|
<div className="flex items-center space-x-4">
|
|
|
|
|
<span className="font-medium" style={{ color: 'var(--text-primary)' }}>
|
|
|
|
|
{collection.cardCount} cards
|
|
|
|
|
</span>
|
|
|
|
|
<span className="font-medium" style={{ color: 'var(--accent-ember)' }}>
|
|
|
|
|
{formatCurrency(collection.value)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<span className="text-xs">
|
|
|
|
|
{formatDate(collection.createdAt)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Tags */}
|
|
|
|
|
{collection.tags && collection.tags.length > 0 && (
|
|
|
|
|
<div className="flex flex-wrap gap-1">
|
|
|
|
|
{collection.tags.slice(0, 2).map((tag, index) => (
|
|
|
|
|
<span
|
|
|
|
|
key={index}
|
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>
2026-06-04 13:55:29 -04:00
|
|
|
className="px-2 py-1 rounded-xl text-xs font-medium"
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: 'var(--bg-tertiary)',
|
|
|
|
|
color: 'var(--text-secondary)',
|
2025-07-27 14:17:13 -04:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{tag}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
{collection.tags.length > 2 && (
|
|
|
|
|
<span
|
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>
2026-06-04 13:55:29 -04:00
|
|
|
className="px-2 py-1 rounded-xl text-xs font-medium"
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: 'var(--bg-tertiary)',
|
|
|
|
|
color: 'var(--text-secondary)',
|
2025-07-27 14:17:13 -04:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
+{collection.tags.length - 2}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Link>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</Layout>
|
|
|
|
|
);
|
|
|
|
|
}
|