feat(design-system): sweep authenticated body-content panels to glass
PR #95/#96 shipped the Liquid Glass foundation (tokens, primitives, gates) plus Layout shell, modals, landing, auth pages, and form CTAs — but body- content panels on authenticated pages (admin Card Editor, admin Card Import, admin Submissions, dashboard, my-cards, settings, scanner panels, card detail price cards, popovers) were still rendering as flat var(--bg-secondary) cards. Result: the admin Tools screen and several core pages looked unchanged after the redesign. This sweep adds a `.glass-panel` / `.glass-panel-strong` utility (<GlassSurface tint=mid/high rim=subtle elevation=ambient/pronounced blur=mid/high> in class form) and applies it across 18 surfaces: * Admin Card Editor view, search panel, form (5 sections), preview * Admin Card Import navigation + 3 body cards + sync panel * Admin Card Submissions list items * Dashboard stat cards + empty-state + grid items (5 surfaces) * My-cards empty-state CTA card * Settings panels (3) * Scanner page settings + grid + queue + bulk toolbar + dialog * Scanner destination picker + camera status banner + disambiguation * Card detail price cards (Current / TCGPlayer / CardKingdom) * Permission indicator tooltips * Collections page header card * Card detail view price cards Also migrates the lingering admin Card Editor "Card Editor / Card Import" nav buttons and the "Save Changes" / "Import Cards" / "Run catalog sync" CTAs to the <Button> primitive (consistent loading + disabled states). Page header bands (full-bleed strips with border-bottom on dashboard, my-cards, cards, collections, community/collections, collection/[id]) are intentionally left solid — they're not card-shaped surfaces and stacking glass-on-glass directly below the already-glass topbar would muddy the hierarchy. Tests: lint clean, vitest 104/104, build green. The visual diff baseline will need refresh because the homepage spec is unaffected (it targets the unauthenticated landing page) but the dashboard/ admin/scanner surfaces will diff if/when we add baselines for them. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
926ce7a45b
commit
d269bd29c1
19 changed files with 133 additions and 139 deletions
|
|
@ -205,8 +205,7 @@ export default function CameraScannerView({
|
||||||
|
|
||||||
{isStreaming && (
|
{isStreaming && (
|
||||||
<div
|
<div
|
||||||
className="rounded-xl p-3 border"
|
className="glass-panel rounded-xl p-3"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)', borderColor: 'var(--border)' }}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,7 @@ export default function CardDetailView(props) {
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{card.oracle_text && (
|
{card.oracle_text && (
|
||||||
<div className="p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-4 rounded-xl">
|
||||||
<p style={{ color: 'var(--text-primary)' }}>{card.oracle_text}</p>
|
<p style={{ color: 'var(--text-primary)' }}>{card.oracle_text}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -350,7 +350,7 @@ export default function CardDetailView(props) {
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Price Graph Placeholder */}
|
{/* Price Graph Placeholder */}
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<h4 className="text-lg font-semibold mb-6" style={{ color: 'var(--text-primary)' }}>
|
<h4 className="text-lg font-semibold mb-6" style={{ color: 'var(--text-primary)' }}>
|
||||||
Price Trend
|
Price Trend
|
||||||
</h4>
|
</h4>
|
||||||
|
|
@ -364,11 +364,7 @@ export default function CardDetailView(props) {
|
||||||
{/* Price Statistics Cards */}
|
{/* Price Statistics Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
{/* Current Price */}
|
{/* Current Price */}
|
||||||
<div className="p-6 rounded-xl border transition-all duration-200 hover:shadow-lg hover:scale-105"
|
<div className="glass-panel p-6 rounded-xl transition-all duration-200 hover:shadow-lg hover:scale-105">
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-secondary)',
|
|
||||||
borderColor: 'var(--border)'
|
|
||||||
}}>
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-2xl mb-2">💰</div>
|
<div className="text-2xl mb-2">💰</div>
|
||||||
<h5 className="font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
|
<h5 className="font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
|
@ -393,11 +389,7 @@ export default function CardDetailView(props) {
|
||||||
href={`https://www.tcgplayer.com/search/${card.game.toLowerCase()}/product?productName=${encodeURIComponent(card.name)}`}
|
href={`https://www.tcgplayer.com/search/${card.game.toLowerCase()}/product?productName=${encodeURIComponent(card.name)}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="p-6 rounded-xl border transition-all duration-200 hover:shadow-lg hover:scale-105"
|
className="glass-panel p-6 rounded-xl transition-all duration-200 hover:shadow-lg hover:scale-105"
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-secondary)',
|
|
||||||
borderColor: 'var(--border)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-2xl">🃏</span>
|
<span className="text-2xl">🃏</span>
|
||||||
|
|
@ -415,11 +407,7 @@ export default function CardDetailView(props) {
|
||||||
href={`https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(card.name + ' ' + card.game)}`}
|
href={`https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(card.name + ' ' + card.game)}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="p-6 rounded-xl border transition-all duration-200 hover:shadow-lg hover:scale-105"
|
className="glass-panel p-6 rounded-xl transition-all duration-200 hover:shadow-lg hover:scale-105"
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-secondary)',
|
|
||||||
borderColor: 'var(--border)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-2xl">🛒</span>
|
<span className="text-2xl">🛒</span>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
|
import { Button } from './ui';
|
||||||
|
|
||||||
const RARITY_OPTIONS = [
|
const RARITY_OPTIONS = [
|
||||||
'common', 'uncommon', 'rare', 'mythic', 'legendary',
|
'common', 'uncommon', 'rare', 'mythic', 'legendary',
|
||||||
|
|
@ -23,17 +23,15 @@ export default function CardEditorForm({
|
||||||
<h2 className="text-xl font-semibold" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-xl font-semibold" style={{ color: 'var(--text-primary)' }}>
|
||||||
Edit Card Details
|
Edit Card Details
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
onClick={onSave}
|
onClick={onSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className={`px-6 py-3 rounded-xl font-medium transition-all duration-200 ${
|
loading={saving}
|
||||||
saving
|
|
||||||
? 'opacity-50 cursor-not-allowed'
|
|
||||||
: 'gradient-bg-ember text-white hover:shadow-lg'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{saving ? 'Saving...' : 'Save Changes'}
|
{saving ? 'Saving...' : 'Save Changes'}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{message && (
|
{message && (
|
||||||
|
|
@ -48,7 +46,7 @@ export default function CardEditorForm({
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Basic Information
|
Basic Information
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -161,7 +159,7 @@ export default function CardEditorForm({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Game Mechanics */}
|
{/* Game Mechanics */}
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Game Mechanics
|
Game Mechanics
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -274,7 +272,7 @@ export default function CardEditorForm({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Card Text */}
|
{/* Card Text */}
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Card Text
|
Card Text
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -298,7 +296,7 @@ export default function CardEditorForm({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Images */}
|
{/* Images */}
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Card Images
|
Card Images
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -341,7 +339,7 @@ export default function CardEditorForm({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pricing */}
|
{/* Pricing */}
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Pricing Information
|
Pricing Information
|
||||||
</h3>
|
</h3>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export default function CardEditorPreview({ formData }) {
|
||||||
<h2 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Card Preview
|
Card Preview
|
||||||
</h2>
|
</h2>
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<div className="w-full max-w-xs mx-auto">
|
<div className="w-full max-w-xs mx-auto">
|
||||||
<div
|
<div
|
||||||
className="w-full rounded-lg overflow-hidden shadow-lg"
|
className="w-full rounded-lg overflow-hidden shadow-lg"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
|
/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
|
||||||
|
import { SearchBar } from './ui';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin card editor: search input + result grid for picking a card to edit.
|
* Admin card editor: search input + result grid for picking a card to edit.
|
||||||
|
|
@ -11,24 +12,21 @@ export default function CardEditorSearchPanel({
|
||||||
onSelectCard,
|
onSelectCard,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-8 p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel mb-8 p-6 rounded-xl">
|
||||||
<h2 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h2
|
||||||
|
className="text-xl font-semibold mb-4"
|
||||||
|
style={{ color: 'var(--text-primary)' }}
|
||||||
|
>
|
||||||
Find Card to Edit
|
Find Card to Edit
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="flex gap-4 mb-4">
|
<div className="flex gap-4 mb-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<input
|
<SearchBar
|
||||||
type="text"
|
|
||||||
placeholder="Search for cards by name..."
|
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => onSearchQueryChange(e.target.value)}
|
onChange={(e) => onSearchQueryChange(e.target.value)}
|
||||||
className="w-full p-3 rounded-lg border transition-all duration-200"
|
onClear={() => onSearchQueryChange('')}
|
||||||
style={{
|
placeholder="Search for cards by name…"
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -36,8 +34,8 @@ export default function CardEditorSearchPanel({
|
||||||
{searchLoading && (
|
{searchLoading && (
|
||||||
<div className="text-center py-4">
|
<div className="text-center py-4">
|
||||||
<div
|
<div
|
||||||
className="animate-spin rounded-full h-8 w-8 border-b-2 mx-auto"
|
className="motion-essential animate-spin rounded-full h-8 w-8 border-b-2 mx-auto"
|
||||||
style={{ borderColor: 'var(--text-accent)' }}
|
style={{ borderColor: 'var(--accent-ember)' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -45,19 +43,21 @@ export default function CardEditorSearchPanel({
|
||||||
{searchResults.length > 0 && (
|
{searchResults.length > 0 && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{searchResults.map((searchCard) => (
|
{searchResults.map((searchCard) => (
|
||||||
<div
|
<button
|
||||||
|
type="button"
|
||||||
key={searchCard.id}
|
key={searchCard.id}
|
||||||
onClick={() => onSelectCard(searchCard)}
|
onClick={() => onSelectCard(searchCard)}
|
||||||
className="p-4 rounded-lg border cursor-pointer transition-all duration-200 hover:shadow-lg hover:scale-105"
|
className="glass-panel p-4 rounded-lg cursor-pointer text-left hover:scale-[1.02] focus:outline-none focus-visible:ring-2"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'var(--bg-primary)',
|
transition:
|
||||||
borderColor: 'var(--border)',
|
'transform var(--motion-duration-default) var(--motion-ease-out), box-shadow var(--motion-duration-default) var(--motion-ease-out)',
|
||||||
|
'--tw-ring-color': 'var(--accent-ember)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
className="w-12 h-16 rounded-lg overflow-hidden flex-shrink-0"
|
className="w-12 h-16 rounded-lg overflow-hidden flex-shrink-0"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)' }}
|
style={{ backgroundColor: 'var(--bg-tertiary)' }}
|
||||||
>
|
>
|
||||||
{searchCard.image_url ? (
|
{searchCard.image_url ? (
|
||||||
<img
|
<img
|
||||||
|
|
@ -75,7 +75,10 @@ export default function CardEditorSearchPanel({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="font-semibold" style={{ color: 'var(--text-primary)' }}>
|
<h3
|
||||||
|
className="font-semibold"
|
||||||
|
style={{ color: 'var(--text-primary)' }}
|
||||||
|
>
|
||||||
{searchCard.name}
|
{searchCard.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
|
@ -86,7 +89,7 @@ export default function CardEditorSearchPanel({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import CardEditorForm from './CardEditorForm';
|
import CardEditorForm from './CardEditorForm';
|
||||||
import CardEditorPreview from './CardEditorPreview';
|
import CardEditorPreview from './CardEditorPreview';
|
||||||
import CardEditorSearchPanel from './CardEditorSearchPanel';
|
import CardEditorSearchPanel from './CardEditorSearchPanel';
|
||||||
|
import { Button } from './ui';
|
||||||
|
|
||||||
export default function CardEditorView({
|
export default function CardEditorView({
|
||||||
card,
|
card,
|
||||||
|
|
@ -20,31 +21,26 @@ export default function CardEditorView({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-6 py-8">
|
<div className="container mx-auto px-6 py-8">
|
||||||
<div className="mb-8 p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel mb-8 p-4 rounded-xl">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h1 className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
<h1 className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
||||||
Admin Tools
|
Admin Tools
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant="primary"
|
||||||
onClick={() => router.push('/admin/card-editor')}
|
onClick={() => router.push('/admin/card-editor')}
|
||||||
className="px-4 py-2 rounded-lg font-medium gradient-bg-ember text-white"
|
leadingIcon={<span aria-hidden="true">🖊️</span>}
|
||||||
>
|
>
|
||||||
🖊️ Card Editor
|
Card Editor
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant="secondary"
|
||||||
onClick={() => router.push('/admin/card-import')}
|
onClick={() => router.push('/admin/card-import')}
|
||||||
className="px-4 py-2 rounded-lg font-medium transition-all duration-200 border"
|
leadingIcon={<span aria-hidden="true">📥</span>}
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
📥 Card Import
|
Card Import
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -86,7 +82,7 @@ export default function CardEditorView({
|
||||||
|
|
||||||
{showEmptyState && (
|
{showEmptyState && (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<div className="text-6xl mb-4">🔍</div>
|
<div className="text-6xl mb-4" aria-hidden="true">🔍</div>
|
||||||
<h2 className="text-2xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-2xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
Search for a Card to Edit
|
Search for a Card to Edit
|
||||||
</h2>
|
</h2>
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ export default function CardsPageView(props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Filters */}
|
{/* Quick Filters */}
|
||||||
<div className="p-4 sm:p-6" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-4 sm:p-6">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-base sm:text-lg font-semibold mb-3" style={{ color: 'var(--text-primary)' }}>
|
<h3 className="text-base sm:text-lg font-semibold mb-3" style={{ color: 'var(--text-primary)' }}>
|
||||||
Quick Filters
|
Quick Filters
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export default function CollectionsPageView(props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-6" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6">
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
<div className="flex flex-col md:flex-row gap-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<SearchBar
|
<SearchBar
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,7 @@ export default function PermissionIndicator({ userRole, isPublic, showTooltip =
|
||||||
{/* Role Tooltip */}
|
{/* Role Tooltip */}
|
||||||
{showDetails && showTooltip && (
|
{showDetails && showTooltip && (
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-full left-0 mb-2 p-3 rounded-lg shadow-lg z-10 min-w-48"
|
className="glass-panel-strong absolute bottom-full left-0 mb-2 p-3 rounded-lg z-10 min-w-48"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)', border: '1px solid var(--border)' }}
|
|
||||||
>
|
>
|
||||||
<div className="text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
<div className="text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
{roleInfo.icon} {roleInfo.label} Permissions:
|
{roleInfo.icon} {roleInfo.label} Permissions:
|
||||||
|
|
@ -118,8 +117,7 @@ export default function PermissionIndicator({ userRole, isPublic, showTooltip =
|
||||||
{/* Visibility Tooltip */}
|
{/* Visibility Tooltip */}
|
||||||
{showDetails && showTooltip && (
|
{showDetails && showTooltip && (
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-full right-0 mb-2 p-3 rounded-lg shadow-lg z-10 min-w-48"
|
className="glass-panel-strong absolute bottom-full right-0 mb-2 p-3 rounded-lg z-10 min-w-48"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)', border: '1px solid var(--border)' }}
|
|
||||||
>
|
>
|
||||||
<div className="text-sm font-medium mb-1" style={{ color: 'var(--text-primary)' }}>
|
<div className="text-sm font-medium mb-1" style={{ color: 'var(--text-primary)' }}>
|
||||||
{visibilityInfo.icon} {visibilityInfo.label}
|
{visibilityInfo.icon} {visibilityInfo.label}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ export default function ScanDisambiguationDialog({
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60 p-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60 p-4">
|
||||||
<div
|
<div
|
||||||
ref={dialogRef}
|
ref={dialogRef}
|
||||||
className="max-w-lg w-full rounded-xl border p-6 max-h-[80vh] overflow-y-auto"
|
className="glass-panel-strong max-w-lg w-full rounded-xl p-6 max-h-[80vh] overflow-y-auto"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)', borderColor: 'var(--border)' }}
|
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="disambiguation-title"
|
aria-labelledby="disambiguation-title"
|
||||||
|
|
@ -49,7 +48,7 @@ export default function ScanDisambiguationDialog({
|
||||||
{candidate.image_url ? (
|
{candidate.image_url ? (
|
||||||
<img src={candidate.image_url} alt="" className="w-12 h-16 object-cover rounded" />
|
<img src={candidate.image_url} alt="" className="w-12 h-16 object-cover rounded" />
|
||||||
) : (
|
) : (
|
||||||
<div className="w-12 h-16 rounded flex items-center justify-center text-xs" style={{ backgroundColor: 'var(--bg-secondary)' }}>🃏</div>
|
<div className="glass-panel w-12 h-16 rounded flex items-center justify-center text-xs">🃏</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium" style={{ color: 'var(--text-primary)' }}>{candidate.name}</div>
|
<div className="font-medium" style={{ color: 'var(--text-primary)' }}>{candidate.name}</div>
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,7 @@ export default function ScannedCardItem({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative flex-shrink-0">
|
<div className="relative flex-shrink-0">
|
||||||
<div className="w-20 h-28 rounded-lg overflow-hidden flex items-center justify-center"
|
<div className="glass-panel w-20 h-28 rounded-lg overflow-hidden flex items-center justify-center">
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
|
||||||
{card.image_url ? (
|
{card.image_url ? (
|
||||||
<img src={card.image_url} alt={card.name} className="w-full h-full object-cover" />
|
<img src={card.image_url} alt={card.name} className="w-full h-full object-cover" />
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,7 @@ export default function ScannerDestinationPicker({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className="mx-6 mb-4 rounded-xl border p-4"
|
className="glass-panel mx-6 mb-4 rounded-xl p-4"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)', borderColor: 'var(--border)' }}
|
|
||||||
aria-label="Scan destination"
|
aria-label="Scan destination"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,7 @@ export default function ScannerPageView({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="mx-6 mb-4 rounded-xl border p-4 flex flex-wrap items-end gap-4"
|
className="glass-panel mx-6 mb-4 rounded-xl p-4 flex flex-wrap items-end gap-4"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)', borderColor: 'var(--border)' }}
|
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-semibold uppercase tracking-wide mb-1" style={{ color: 'var(--text-secondary)' }}>
|
<h2 className="text-sm font-semibold uppercase tracking-wide mb-1" style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
|
@ -99,8 +98,7 @@ export default function ScannerPageView({
|
||||||
<div className="flex-1 grid grid-cols-1 lg:grid-cols-5 gap-6 px-6 pb-6">
|
<div className="flex-1 grid grid-cols-1 lg:grid-cols-5 gap-6 px-6 pb-6">
|
||||||
<div className="lg:col-span-3 flex flex-col">
|
<div className="lg:col-span-3 flex flex-col">
|
||||||
<div
|
<div
|
||||||
className="flex-1 rounded-xl p-6 flex flex-col"
|
className="glass-panel flex-1 rounded-xl p-6 flex flex-col"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)', border: '1px solid var(--border)' }}
|
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h2 className="text-xl font-semibold" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-xl font-semibold" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
|
@ -126,8 +124,7 @@ export default function ScannerPageView({
|
||||||
|
|
||||||
<div className="lg:col-span-2 flex flex-col">
|
<div className="lg:col-span-2 flex flex-col">
|
||||||
<div
|
<div
|
||||||
className="flex-1 rounded-xl p-6 flex flex-col"
|
className="glass-panel flex-1 rounded-xl p-6 flex flex-col"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)', border: '1px solid var(--border)' }}
|
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h2 className="text-xl font-semibold" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-xl font-semibold" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
|
@ -196,14 +193,9 @@ export default function ScannerPageView({
|
||||||
{queue.selectedCards.size > 0 && (
|
{queue.selectedCards.size > 0 && (
|
||||||
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 z-50">
|
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 z-50">
|
||||||
<div
|
<div
|
||||||
className="rounded-2xl shadow-2xl border px-6 py-4 flex items-center gap-4 max-w-4xl"
|
className="glass-panel-strong rounded-2xl px-6 py-4 flex items-center gap-4 max-w-4xl"
|
||||||
role="toolbar"
|
role="toolbar"
|
||||||
aria-label="Bulk actions for selected scanned cards"
|
aria-label="Bulk actions for selected scanned cards"
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-secondary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
backdropFilter: 'blur(10px)',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2" aria-live="polite" aria-atomic="true">
|
<div className="flex items-center gap-2" aria-live="polite" aria-atomic="true">
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import Layout from '../../components/Layout';
|
import Layout from '../../components/Layout';
|
||||||
import AdminProtected from '../../components/AdminProtected';
|
import AdminProtected from '../../components/AdminProtected';
|
||||||
|
import { Button } from '../../components/ui';
|
||||||
|
|
||||||
const CardImport = () => {
|
const CardImport = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -128,29 +129,26 @@ const CardImport = () => {
|
||||||
<Layout user={user}>
|
<Layout user={user}>
|
||||||
<div className="container mx-auto px-6 py-8">
|
<div className="container mx-auto px-6 py-8">
|
||||||
{/* Admin Navigation */}
|
{/* Admin Navigation */}
|
||||||
<div className="mb-8 p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel mb-8 p-4 rounded-xl">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h1 className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
<h1 className="text-2xl font-bold" style={{ color: 'var(--text-primary)' }}>
|
||||||
Admin Tools
|
Admin Tools
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<button
|
<Button
|
||||||
|
variant="secondary"
|
||||||
onClick={() => router.push('/admin/card-editor')}
|
onClick={() => router.push('/admin/card-editor')}
|
||||||
className="px-4 py-2 rounded-lg font-medium transition-all duration-200 border"
|
leadingIcon={<span aria-hidden="true">🖊️</span>}
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--bg-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
color: 'var(--text-primary)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
🖊️ Card Editor
|
Card Editor
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
|
variant="primary"
|
||||||
onClick={() => router.push('/admin/card-import')}
|
onClick={() => router.push('/admin/card-import')}
|
||||||
className="px-4 py-2 rounded-lg font-medium gradient-bg-ember text-white"
|
leadingIcon={<span aria-hidden="true">📥</span>}
|
||||||
>
|
>
|
||||||
📥 Card Import
|
Card Import
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -166,7 +164,7 @@ const CardImport = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Catalog sync */}
|
{/* Catalog sync */}
|
||||||
<div className="mb-8 p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel mb-8 p-6 rounded-xl">
|
||||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-xl font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
|
@ -177,18 +175,16 @@ const CardImport = () => {
|
||||||
Pending scan submissions for those sets are auto-linked to the catalog when a unique match exists.
|
Pending scan submissions for those sets are auto-linked to the catalog when a unique match exists.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
onClick={handleCatalogSync}
|
onClick={handleCatalogSync}
|
||||||
disabled={isSyncing}
|
disabled={isSyncing}
|
||||||
className={`shrink-0 py-3 px-6 rounded-xl font-medium transition-all duration-200 ${
|
loading={isSyncing}
|
||||||
isSyncing
|
className="shrink-0"
|
||||||
? 'opacity-50 cursor-not-allowed'
|
|
||||||
: 'gradient-bg-ember text-white hover:shadow-lg'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{isSyncing ? 'Syncing catalog…' : 'Run catalog sync'}
|
{isSyncing ? 'Syncing catalog…' : 'Run catalog sync'}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{syncResult && (
|
{syncResult && (
|
||||||
|
|
@ -265,7 +261,7 @@ const CardImport = () => {
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
{/* Import Form */}
|
{/* Import Form */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<h2 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Import Settings
|
Import Settings
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -301,17 +297,16 @@ const CardImport = () => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
onClick={handleImport}
|
onClick={handleImport}
|
||||||
disabled={isImporting || !setCode.trim()}
|
disabled={isImporting || !setCode.trim()}
|
||||||
className={`w-full py-3 px-6 rounded-xl font-medium transition-all duration-200 ${
|
loading={isImporting}
|
||||||
isImporting || !setCode.trim()
|
fullWidth
|
||||||
? 'opacity-50 cursor-not-allowed'
|
|
||||||
: 'gradient-bg-ember text-white hover:shadow-lg'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{isImporting ? 'Importing...' : 'Import Cards'}
|
{isImporting ? 'Importing...' : 'Import Cards'}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -343,7 +338,7 @@ const CardImport = () => {
|
||||||
|
|
||||||
{/* Popular Sets */}
|
{/* Popular Sets */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<h2 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Popular Sets
|
Popular Sets
|
||||||
</h2>
|
</h2>
|
||||||
|
|
@ -402,7 +397,7 @@ const CardImport = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Import Tips */}
|
{/* Import Tips */}
|
||||||
<div className="p-6 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel p-6 rounded-xl">
|
||||||
<h2 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h2 className="text-xl font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Import Tips
|
Import Tips
|
||||||
</h2>
|
</h2>
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,7 @@ function CardSubmissionsAdmin() {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={sub.id}
|
key={sub.id}
|
||||||
className="p-4 rounded-xl border"
|
className="glass-panel p-4 rounded-xl"
|
||||||
style={{ backgroundColor: 'var(--bg-secondary)', borderColor: 'var(--border)' }}
|
|
||||||
>
|
>
|
||||||
<div className="flex justify-between gap-4 mb-2">
|
<div className="flex justify-between gap-4 mb-2">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ export default function Dashboard() {
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||||
<div className="rounded-2xl p-6" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel rounded-2xl p-6">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="p-3 rounded-2xl mr-4" style={{ backgroundColor: 'var(--accent-ember)' }}>
|
<div className="p-3 rounded-2xl mr-4" style={{ backgroundColor: 'var(--accent-ember)' }}>
|
||||||
<svg className="h-8 w-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="h-8 w-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -132,7 +132,7 @@ export default function Dashboard() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-2xl p-6" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel rounded-2xl p-6">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="p-3 rounded-2xl mr-4" style={{ backgroundColor: 'var(--accent-gold)' }}>
|
<div className="p-3 rounded-2xl mr-4" style={{ backgroundColor: 'var(--accent-gold)' }}>
|
||||||
<svg className="h-8 w-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="h-8 w-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -148,7 +148,7 @@ export default function Dashboard() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-2xl p-6" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel rounded-2xl p-6">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="p-3 rounded-2xl mr-4" style={{ backgroundColor: 'var(--accent-flame)' }}>
|
<div className="p-3 rounded-2xl mr-4" style={{ backgroundColor: 'var(--accent-flame)' }}>
|
||||||
<svg className="h-8 w-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="h-8 w-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -170,7 +170,7 @@ export default function Dashboard() {
|
||||||
<h2 className="text-xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>Recent Lists</h2>
|
<h2 className="text-xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>Recent Lists</h2>
|
||||||
{collections.length === 0 ? (
|
{collections.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl flex items-center justify-center" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel w-16 h-16 mx-auto mb-4 rounded-2xl flex items-center justify-center">
|
||||||
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: 'var(--text-secondary)' }}>
|
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: 'var(--text-secondary)' }}>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -189,7 +189,7 @@ export default function Dashboard() {
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{collections.slice(0, 6).map((collection) => (
|
{collections.slice(0, 6).map((collection) => (
|
||||||
<Link key={collection.id} href={`/collection/${collection.slug || collection.id}`}>
|
<Link key={collection.id} href={`/collection/${collection.slug || collection.id}`}>
|
||||||
<div className="rounded-2xl p-6 transition-all duration-200 hover:scale-105 cursor-pointer" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel rounded-2xl p-6 transition-all duration-200 hover:scale-105 cursor-pointer">
|
||||||
<div className="flex items-center mb-4">
|
<div className="flex items-center mb-4">
|
||||||
<div className="w-12 h-12 rounded-xl mr-4 flex items-center justify-center gradient-bg-ember">
|
<div className="w-12 h-12 rounded-xl mr-4 flex items-center justify-center gradient-bg-ember">
|
||||||
<span className="text-white font-bold">
|
<span className="text-white font-bold">
|
||||||
|
|
|
||||||
|
|
@ -432,7 +432,7 @@ export default function MyCards() {
|
||||||
{/* No Cards Found */}
|
{/* No Cards Found */}
|
||||||
{cards.length === 0 && !loading && (
|
{cards.length === 0 && !loading && (
|
||||||
<div className="text-center py-20">
|
<div className="text-center py-20">
|
||||||
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl flex items-center justify-center" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel w-16 h-16 mx-auto mb-4 rounded-2xl flex items-center justify-center">
|
||||||
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: 'var(--text-secondary)' }}>
|
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24" style={{ color: 'var(--text-secondary)' }}>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
|
@ -431,7 +431,7 @@ export default function Settings() {
|
||||||
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
<h3 className="text-lg font-semibold mb-4" style={{ color: 'var(--text-primary)' }}>
|
||||||
Two-Factor Authentication
|
Two-Factor Authentication
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel flex items-center justify-between p-4 rounded-xl">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium" style={{ color: 'var(--text-primary)' }}>
|
<p className="font-medium" style={{ color: 'var(--text-primary)' }}>
|
||||||
Enable 2FA
|
Enable 2FA
|
||||||
|
|
@ -544,7 +544,7 @@ export default function Settings() {
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Email Notifications */}
|
{/* Email Notifications */}
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel flex items-center justify-between p-4 rounded-xl">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium" style={{ color: 'var(--text-primary)' }}>
|
<p className="font-medium" style={{ color: 'var(--text-primary)' }}>
|
||||||
Email Notifications
|
Email Notifications
|
||||||
|
|
@ -568,7 +568,7 @@ export default function Settings() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Marketing Emails */}
|
{/* Marketing Emails */}
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
<div className="glass-panel flex items-center justify-between p-4 rounded-xl">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium" style={{ color: 'var(--text-primary)' }}>
|
<p className="font-medium" style={{ color: 'var(--text-primary)' }}>
|
||||||
Marketing Emails
|
Marketing Emails
|
||||||
|
|
|
||||||
|
|
@ -426,6 +426,35 @@ body {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
.glass-panel — canonical "content card" utility for body
|
||||||
|
surfaces on authenticated pages. The Liquid Glass recipe in
|
||||||
|
class form so pages can apply it without importing the
|
||||||
|
<GlassSurface> primitive for every flat container. Added by
|
||||||
|
glass-body-panels PR after PR #96 left admin/settings/dashboard
|
||||||
|
body cards on solid --bg-secondary.
|
||||||
|
|
||||||
|
Composition matches <GlassSurface tint="mid" rim="subtle"
|
||||||
|
elevation="ambient" blur="mid">. Always pair with rounded-*
|
||||||
|
from Tailwind (the class itself doesn't set border-radius
|
||||||
|
because consumers vary between rounded-xl and rounded-2xl).
|
||||||
|
============================================================ */
|
||||||
|
.glass-panel {
|
||||||
|
background-color: var(--glass-surface-mid);
|
||||||
|
backdrop-filter: blur(var(--glass-blur-mid)) saturate(var(--glass-saturate));
|
||||||
|
-webkit-backdrop-filter: blur(var(--glass-blur-mid)) saturate(var(--glass-saturate));
|
||||||
|
border: 1px solid var(--rim-light-outer);
|
||||||
|
box-shadow: var(--elevation-ambient), inset 0 1px 0 var(--rim-light-inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-panel-strong {
|
||||||
|
background-color: var(--glass-surface-high);
|
||||||
|
backdrop-filter: blur(var(--glass-blur-high)) saturate(var(--glass-saturate));
|
||||||
|
-webkit-backdrop-filter: blur(var(--glass-blur-high)) saturate(var(--glass-saturate));
|
||||||
|
border: 1px solid var(--rim-light-outer);
|
||||||
|
box-shadow: var(--elevation-pronounced), inset 0 1px 0 var(--rim-light-inner);
|
||||||
|
}
|
||||||
|
|
||||||
/* Gradient text effects — Deck Hearth warm palette only.
|
/* Gradient text effects — Deck Hearth warm palette only.
|
||||||
The pre-Deck-Hearth aliases (.glow-blue / .glow-purple / .glow-pink,
|
The pre-Deck-Hearth aliases (.glow-blue / .glow-purple / .glow-pink,
|
||||||
.gradient-text-blue / .gradient-text-purple, .gradient-bg-purple)
|
.gradient-text-blue / .gradient-text-purple, .gradient-bg-purple)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue