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:
Randall Stillwell 2026-06-03 20:44:40 -05:00
parent 926ce7a45b
commit d269bd29c1
19 changed files with 133 additions and 139 deletions

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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"

View file

@ -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>
)} )}

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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}

View file

@ -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>

View file

@ -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" />
) : ( ) : (

View file

@ -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">

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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>

View file

@ -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

View file

@ -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)