From b7ffeef2703abf239d1f3dd56fea9e3734c14746 Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Wed, 3 Jun 2026 20:30:15 -0500 Subject: [PATCH] =?UTF-8?q?feat(design-system):=20finish=20Liquid=20Glass?= =?UTF-8?q?=20=E2=80=94=20close=20all=20remaining=20sub-convoys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 primitive - `CollectionsSuccessModal.js` — wrap in ; 2 Buttons. - `CollectionsEditModal.js` — full + + diff --git a/components/Card3D.js b/components/Card3D.js deleted file mode 100644 index 47fe808..0000000 --- a/components/Card3D.js +++ /dev/null @@ -1,530 +0,0 @@ -/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */ -import { useState } from 'react'; -import { ManaCost } from './ManaSymbols'; -import { VOCAB } from '../lib/collection-vocabulary.js'; - -export default function Card3D({ card, viewMode, getRarityLabel, getRarityColor, useManaSvg = false }) { - - const handleMouseMove = (e) => { - const rect = e.currentTarget.getBoundingClientRect(); - setCardPosition({ - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height - }); - }; - - const handleMouseLeave = () => { - setIsHovered(false); - }; - - const handleMouseEnter = () => { - setIsHovered(true); - }; - - const getTCGColor = (game) => { - const colors = { - 'MTG': '#8B5CF6', // purple - 'Pokemon': '#3B82F6', // blue - 'Lorcana': '#EC4899' // pink - }; - return colors[game] || '#6B7280'; - }; - - const getSetColor = (setName) => { - // Generate a consistent color based on set name - const hash = setName.split('').reduce((a, b) => { - a = ((a << 5) - a) + b.charCodeAt(0); - return a & a; - }, 0); - const hue = Math.abs(hash) % 360; - return `hsl(${hue}, 70%, 60%)`; - }; - - const formatCurrency = (amount) => { - return new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD' - }).format(amount); - }; - - const getRarityGradient = (rarity) => { - const gradients = { - common: 'from-gray-400 to-gray-500', - uncommon: 'from-green-400 to-emerald-500', - rare: 'from-yellow-400 to-orange-500', - mythic: 'from-yellow-400 to-orange-500', - holographic: 'from-red-400 to-pink-500', - enchanted: 'from-purple-400 to-indigo-500', - ultra: 'from-blue-400 to-cyan-500' - }; - return gradients[rarity] || 'from-gray-400 to-gray-500'; - }; - - const getRarityGlow = (rarity) => { - const glows = { - common: '0 0 15px rgba(156, 163, 175, 0.4)', - uncommon: '0 0 15px rgba(34, 197, 94, 0.4)', - rare: '0 0 20px rgba(251, 191, 36, 0.5)', - mythic: '0 0 20px rgba(255, 215, 0, 0.4), 0 0 40px rgba(255, 215, 0, 0.2), 0 0 60px rgba(255, 215, 0, 0.1)', - holographic: '0 0 25px rgba(239, 68, 68, 0.6)', - enchanted: '0 0 20px rgba(168, 85, 247, 0.4), 0 0 40px rgba(168, 85, 247, 0.2), 0 0 60px rgba(168, 85, 247, 0.1)', - ultra: '0 0 25px rgba(59, 130, 246, 0.6)' - }; - return glows[rarity] || '0 0 15px rgba(156, 163, 175, 0.4)'; - }; - - if (viewMode === 'list') { - return ( -
-
-
-
- {card.game} -
-
-
-

- {card.name} -

-

- {card.set_name} • {getRarityLabel(card.rarity)} -

-

- {card.oracle_text || card.card_type} -

-
-
-

- {formatCurrency(card.current_price || 0)} -

- - {getRarityLabel(card.rarity)} - -
-
-
- ); - } - - return ( -
- {/* Card Container with proper ratio and rarity glow */} -
- {/* Particle Effects for All Rarities */} - {card.rarity !== 'common' && ( -
- {/* Get particle count and colors based on rarity */} - {(() => { - const rarityConfig = { - 'mythic': { - particleCount: 16, - sparkleCount: 10, - particleColor: '#FFD700', - sparkleColor: '#FFA500', - glowColor: '#FFD700' - }, - 'enchanted': { - particleCount: 14, - sparkleCount: 8, - particleColor: '#A855F7', - sparkleColor: '#EC4899', - glowColor: '#A855F7' - }, - 'rare': { - particleCount: 10, - sparkleCount: 6, - particleColor: '#3B82F6', - sparkleColor: '#60A5FA', - glowColor: '#3B82F6' - }, - 'uncommon': { - particleCount: 6, - sparkleCount: 4, - particleColor: '#10B981', - sparkleColor: '#34D399', - glowColor: '#10B981' - } - }; - - const config = rarityConfig[card.rarity]; - if (!config) return null; - - return ( - <> - {/* Floating Particles - Edge framing */} -
- {[...Array(config.particleCount)].map((_, i) => { - // Position particles around the card edges - let left, top; - const edgeIndex = i % 4; // 4 edges - const positionOnEdge = Math.floor(i / 4); - - if (edgeIndex === 0) { - // Top edge - left = `${10 + (positionOnEdge * 20)}%`; - top = '2%'; - } else if (edgeIndex === 1) { - // Right edge - left = '98%'; - top = `${10 + (positionOnEdge * 20)}%`; - } else if (edgeIndex === 2) { - // Bottom edge - left = `${10 + (positionOnEdge * 20)}%`; - top = '98%'; - } else { - // Left edge - left = '2%'; - top = `${10 + (positionOnEdge * 20)}%`; - } - - return ( -
- ); - })} -
- - {/* Sparkle Effects - Corner highlights */} -
- {[...Array(config.sparkleCount)].map((_, i) => { - // Position sparkles in the corners and edge centers - let left, top; - if (i < 2) { - // Top corners - left = i === 0 ? '5%' : '95%'; - top = '5%'; - } else if (i < 4) { - // Bottom corners - left = i === 2 ? '5%' : '95%'; - top = '95%'; - } else if (i < 6) { - // Edge centers - left = i === 4 ? '50%' : '50%'; - top = i === 4 ? '5%' : '95%'; - } else { - // Side centers - left = i === 6 ? '5%' : '95%'; - top = '50%'; - } - - return ( -
- ); - })} -
- - {/* Rarity Aura - Blend with overall glow */} -
- - ); - })()} -
- )} - {/* Card Image */} -
- {card.image_url ? ( - {card.name} { - e.target.style.display = 'none'; - e.target.nextSibling.style.display = 'flex'; - }} - /> - ) : null} - - {/* Card Back placeholder when no image */} -
- -
-
- - {/* Owned Quantity Chip - Only show if owned */} - {card.quantity > 0 && ( -
-
- {card.quantity} -
-
- )} - - {/* Favorite Button */} -
- -
-
- - {/* Hover Details Panel */} - {isHovered && ( -
-
- {/* Card Title */} -
-
{card.name}
-
- - {/* Top Section - TCG, Set, Type, HP in 2x2 grid */} -
-
-
TCG
-
{card.game}
-
-
-
Set
-
{card.set_name}
-
- {card.type && ( -
-
Type
-
- {card.game === 'Pokemon' && card.type === 'Lightning' && ( - - )} - {card.type} -
-
- )} - {card.hp && ( -
-
HP
-
{card.hp}
-
- )} -
- - {/* Card Type and Form */} -
-
-
Card type
-
{card.card_type || 'Card'}
-
- {card.form && ( -
-
Form
-
{card.form}
-
- )} -
- - {/* Cost to Play */} - {card.mana_cost && ( -
-
Cost to Play
- -
- )} - - {/* Card Rule Section */} - {card.game === 'Pokemon' && card.form && card.form.includes('V') && ( -
-
Card rule
-
- V rule: When your Pokémon V is Knocked Out, your opponent takes 2 Prize cards. -
-
- )} - - {/* Abilities/Attacks/Moves Section */} - {card.oracle_text && ( -
-
Card Text
-
{card.oracle_text}
-
- )} - - {/* Quote Section */} - {card.flavor_text && ( -
-
Quote
-
"{card.flavor_text}"
-
- )} - - {/* Current Price */} - {card.current_price && ( -
-
Current Price
-
{formatCurrency(card.current_price)}
-
- )} - - {/* Bottom Section - Weaknesses and Retreat */} -
- {card.weakness && ( -
-
Weaknesses
-
- {card.game === 'Pokemon' && ( - 👊 - )} - {card.weakness} -
-
- )} - {card.retreat_cost && ( -
-
Retreat
-
- {card.game === 'Pokemon' && ( - <> - - - - )} - {card.retreat_cost} -
-
- )} -
- - {/* Decks and Collections */} - {(card.decks && card.decks.length > 0) || (card.collections && card.collections.length > 0) && ( -
-
Included In
-
- {card.decks && card.decks.length > 0 && ( -
- Decks: {card.decks.join(', ')} -
- )} - {card.collections && card.collections.length > 0 && ( -
- {VOCAB.LISTS}: {card.collections.join(', ')} -
- )} -
-
- )} -
-
- )} -
- ); -} - -// Card Back Component for placeholders -function CardBack({ game }) { - const getCardBackImage = () => { - switch (game) { - case 'MTG': - return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjOEI0NTEzIi8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiNBMDUyMkQiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiM0QTkwRTIiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk1BR0lDPC90ZXh0Pgo8dGV4dCB4PSIxMDAiIHk9IjQ1IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5UaGUgR2F0aGVyaW5nPC90ZXh0Pgo8Y2lyY2xlIGN4PSI3MCIgY3k9IjcwIiByPSI0IiBmaWxsPSJ3aGl0ZSIvPgo8Y2lyY2xlIGN4PSIxMzAiIGN4PSI3MCIgcj0iNCIgZmlsbD0iI0Y1OTlFMEIiLz4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iODAiIHI9IjQiIGZpbGw9IiM0QTkwRTIiLz4KPGNpcmNsZSBjeD0iNzAiIGN5PSI5MCIgcj0iNCIgZmlsbD0iIzEwQjk4MSIvPgo8Y2lyY2xlIGN4PSIxMzAiIGN5PSI5MCIgcj0iNCIgZmlsbD0iYmxhY2siLz4KPHRleHQgeD0iMTAwIiB5PSIxMjAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiMyMEIyQUEiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkRFQ0tNQVNURVI8L3RleHQ+Cjwvc3ZnPgo='; - case 'Pokemon': - return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjRkY2QjZCIi8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiNGRjhFOEUiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiNGRkQ3MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlBPS0VNT048L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iNDUiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlRyYWRpbmcgQ2FyZCBHYW1lPC90ZXh0Pgo8Y2lyY2xlIGN4PSIxMDAiIGN5PSI3MCIgcj0iMjAiIGZpbGw9IndoaXRlIi8+Cjx0ZXh0IHg9IjEwMCIgeT0iNzgiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZmlsbD0iIzFGN0Y3RiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+4pePPC90ZXh0Pgo8dGV4dCB4PSIxMDAiIHk9IjEyMCIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEwIiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0iI0ZGRDcwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+R0FNRSBGUkVBSzwvdGV4dD4KPC9zdmc+Cg=='; - case 'Lorcana': - return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjRUM0ODk5Ii8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiNGNDcyQjYiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IiNGQkJGMjQiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkRJU05FWTwvdGV4dD4KPHRleHQgeD0iMTAwIiB5PSI0NSIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+TG9yY2FuYTwvdGV4dD4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iNzAiIHI9IjIwIiBmaWxsPSJ3aGl0ZSIvPgo8dGV4dCB4PSIxMDAiIHk9Ijc4IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjQiIGZpbGw9IiMxRjdGN0YiIHRleHQtYW5jaG9yPSJtaWRkbGUiPvCfkqQ8L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iMTIwIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTAiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSIjRjU5RTBCIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5SQVZFTlNERVJHPzwvdGV4dD4KPC9zdmc+Cg=='; - default: - return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE0MyIgdmlld0JveD0iMCAwIDIwMCAxNDMiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTQzIiBmaWxsPSIjNkI3MjgwIi8+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSIxMjMiIGZpbGw9IiM5Q0EzQUYiIHJ4PSI4Ii8+Cjx0ZXh0IHg9IjEwMCIgeT0iMzAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9ImJvbGQiIGZpbGw9IndoaXRlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5UQ0c8L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iNDUiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMiIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlRyYWRpbmcgQ2FyZDwvdGV4dD4KPGNpcmNsZSBjeD0iMTAwIiBjeT0iNzAiIHI9IjIwIiBmaWxsPSJ3aGl0ZSIvPgo8dGV4dCB4PSIxMDAiIHk9Ijc4IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjQiIGZpbGw9IiMxRjdGN0YiIHRleHQtYW5jaG9yPSJtaWRkbGUiPvCfkqQ8L3RleHQ+Cjx0ZXh0IHg9IjEwMCIgeT0iMTIwIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTAiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSIjNkI3MjgwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5DQVJEPC90ZXh0Pgo8L3N2Zz4K'; - } - }; - - return ( -
- {`${game} -
- ); -} diff --git a/components/CardDetailDeckModal.js b/components/CardDetailDeckModal.js index d270bda..ad5b5e1 100644 --- a/components/CardDetailDeckModal.js +++ b/components/CardDetailDeckModal.js @@ -1,3 +1,5 @@ +import { Modal, Button } from './ui'; + export default function CardDetailDeckModal({ isOpen, card, @@ -7,69 +9,54 @@ export default function CardDetailDeckModal({ onAdd, onClose, }) { - if (!isOpen || !card) { - return null; - } + if (!card) return null; return ( -
-
-

- Add to Deck -

-
- - -
-
- - -
+ +
+ +
-
+
+ + +
+ ); } diff --git a/components/CardDetailQuantityModal.js b/components/CardDetailQuantityModal.js index 8e9f3dd..a370b76 100644 --- a/components/CardDetailQuantityModal.js +++ b/components/CardDetailQuantityModal.js @@ -54,7 +54,7 @@ export default function CardDetailQuantityModal({ diff --git a/components/CardEditorForm.js b/components/CardEditorForm.js index d329ac8..50e074a 100644 --- a/components/CardEditorForm.js +++ b/components/CardEditorForm.js @@ -29,7 +29,7 @@ export default function CardEditorForm({ className={`px-6 py-3 rounded-xl font-medium transition-all duration-200 ${ saving ? 'opacity-50 cursor-not-allowed' - : 'gradient-bg-purple text-white hover:shadow-lg' + : 'gradient-bg-ember text-white hover:shadow-lg' }`} > {saving ? 'Saving...' : 'Save Changes'} diff --git a/components/CardEditorView.js b/components/CardEditorView.js index da35dc4..de3f8a2 100644 --- a/components/CardEditorView.js +++ b/components/CardEditorView.js @@ -29,7 +29,7 @@ export default function CardEditorView({ diff --git a/components/CollectionEditModal.js b/components/CollectionEditModal.js index 425961e..7c587cc 100644 --- a/components/CollectionEditModal.js +++ b/components/CollectionEditModal.js @@ -1,3 +1,5 @@ +import { Modal, Input, Button } from './ui'; + export default function CollectionEditModal({ isOpen, editForm, @@ -5,106 +7,81 @@ export default function CollectionEditModal({ onClose, onSave, }) { - if (!isOpen) { - return null; - } - return ( -
-
-

- Edit List -

-
-
- - setEditForm({ ...editForm, name: e.target.value })} - placeholder="Enter list name" - /> -
-
- -