feat: Introduce card size slider for unified scaling across grid and stack views, and add smart preview suppression.

This commit is contained in:
2025-12-17 01:20:17 +01:00
parent 58288e5195
commit f9819b324e
8 changed files with 98 additions and 9 deletions

View File

@@ -55,3 +55,6 @@
- [Entrance Animation Fix](./devlog/2025-12-17-021500_entrance_animation_fix.md): Completed. Implemented 'isMounted' state to ensuring scale-in animation triggers correctly on first render.
- [Foil Bug Fix](./devlog/2025-12-17-022000_foil_bug_fix.md): Completed. Fixed regression where foil animations applied to non-foil cards on desktop.
- [Universal Foil Application](./devlog/2025-12-17-022500_universal_foil_application.md): Completed. Applied foil animation to Grid View and Stack View card thumbnails.
- [Smart Preview Suppression](./devlog/2025-12-17-023000_smart_preview_suppression.md): Completed. Disabled hover preview for card elements that are already rendered large enough on screen.
- [Compact Card Layout](./devlog/2025-12-17-023500_compact_card_layout.md): Completed. Decreased card sizes in Grid and Stack views for a denser UI.
- [View Scale Slider](./devlog/2025-12-17-024000_view_scale_slider.md): Completed. Added a slider to dynamically adjust card dimensions, synced across Grid and Stack views.

View File

@@ -0,0 +1,16 @@
# Intelligent Preview Suppression
## Objective
Prevent the card preview popup from appearing when the user hovers over a card that is already displayed at a significantly large size on the screen (e.g., in a large grid view), reducing UI clutter.
## Changes
- Modified `CardHoverWrapper` in `src/client/src/components/CardPreview.tsx`:
- Updated `handleMouseEnter` to inspect the dimensions of the hovered element using `getBoundingClientRect`.
- Implemented a threshold check: `Width > 240px` AND `Height > 300px`.
- **Logic**:
- **Large Grid Items**: If a card in the grid is rendered wider than 240px and taller than 300px, the hover preview is suppressed.
- **List Items**: Even if a list row is wide (e.g., 800px), its height is small (e.g., 40px), so the preview **will still appear**.
- **Small Thumbnails**: Small grid items or stack views usually fall below this threshold, ensuring the preview appears when needed.
## Result
The system now intelligently hides the preview when it is redundant, creating a cleaner experience on large desktop screens while maintaining necessary functionality for smaller thumbnails and list views.

View File

@@ -0,0 +1,18 @@
# Compact Card Layout
## Objective
Slightly resize the card visualizations in both Grid and Stack views to allow more cards to fit on the screen, creating a denser and more "compact" interface as requested.
## Changes
- **Pack Grid View** (`src/client/src/components/PackCard.tsx`):
- Increased the column density across all breakpoints:
- Base: `grid-cols-2` -> `grid-cols-3`
- Small: `grid-cols-3` -> `grid-cols-4`
- Medium: `grid-cols-4` -> `grid-cols-5`
- Large: `grid-cols-5` -> `grid-cols-6`
- This reduces the individual card width, making them visually smaller.
- **Stack / Deck View** (`src/client/src/components/StackView.tsx`):
- Reduced the fixed width of each stack column from `w-44` (176px) to `w-36` (144px).
## Result
Cards appear slightly smaller ("a little more smaller"), providing a broader overview of the pool and deck without requiring as much scrolling. This works in tandem with the "Smart Preview Suppression" (which will likely now re-enable previews for these smaller cards, aiding readability).

View File

@@ -0,0 +1,19 @@
# View Scale Slider
## Objective
Provide the user with granular control over card thumbnail sizes across the application, ensuring consistency between Grid and Stack views.
## Changes
- **CubeManager**:
- Added a new `cardWidth` state variable, persisted to `localStorage` (default `140px`).
- Introduced a **Range Slider** in the top-right control toolbar (visible on desktop) allowing adjustment from 100px to 300px.
- Passed `cardWidth` down to `PackCard`.
- **PackCard (Grid View)**:
- Replaced the responsive `grid-cols-*` logic with a `flex flex-wrap` layout.
- Each card container now receives an explicit `style={{ width: cardWidth }}`.
- **StackView (Stack View)**:
- Accepted `cardWidth` prop.
- Applied `style={{ width: cardWidth }}` to the column containers, dynamically ensuring that stacks resize in sync with the grid view setting.
## Result
Users can now drag a slider to instantly resize all card thumbnails on the screen. This allows for customized density—make cards huge to admire the art, or tiny to see the entire cube/pool at a glance—with perfect size synchronization between the different view modes.

View File

@@ -125,8 +125,17 @@ export const CardHoverWrapper: React.FC<{ card: DraftCard; children: React.React
setCoords({ x: e.clientX, y: e.clientY });
};
const handleMouseEnter = () => {
if (!isMobile) setIsHovering(true);
const handleMouseEnter = (e: React.MouseEvent) => {
if (isMobile) return;
// Check if the card is already "big enough" on screen
const rect = e.currentTarget.getBoundingClientRect();
// Width > 240 && Height > 300 targets large grid items but excludes thin list rows
if (rect.width > 240 && rect.height > 300) {
return;
}
setIsHovering(true);
};
const handleMouseLeave = () => {

View File

@@ -6,6 +6,7 @@ import { StackView } from './StackView';
interface PackCardProps {
pack: Pack;
viewMode: 'list' | 'grid' | 'stack';
cardWidth?: number;
}
import { CardHoverWrapper, FoilOverlay } from './CardPreview';
@@ -41,7 +42,7 @@ const ListItem: React.FC<{ card: DraftCard }> = ({ card }) => {
);
};
export const PackCard: React.FC<PackCardProps> = ({ pack, viewMode }) => {
export const PackCard: React.FC<PackCardProps> = ({ pack, viewMode, cardWidth = 150 }) => {
const mythics = pack.cards.filter(c => c.rarity === 'mythic');
const rares = pack.cards.filter(c => c.rarity === 'rare');
const uncommons = pack.cards.filter(c => c.rarity === 'uncommon');
@@ -97,10 +98,10 @@ export const PackCard: React.FC<PackCardProps> = ({ pack, viewMode }) => {
)}
{viewMode === 'grid' && (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3">
<div className="flex flex-wrap gap-3">
{pack.cards.map((card) => (
<CardHoverWrapper key={card.id} card={card}>
<div className="relative group bg-slate-900 rounded-lg">
<div style={{ width: cardWidth }} className="relative group bg-slate-900 rounded-lg shrink-0">
{/* Visual Card */}
<div className={`relative aspect-[2.5/3.5] overflow-hidden rounded-lg shadow-xl border transition-all duration-200 group-hover:ring-2 group-hover:ring-purple-400 group-hover:shadow-purple-500/30 cursor-pointer ${isFoil(card) ? 'border-purple-400 shadow-purple-500/20' : 'border-slate-800'}`}>
{isFoil(card) && <FoilOverlay />}
@@ -126,7 +127,7 @@ export const PackCard: React.FC<PackCardProps> = ({ pack, viewMode }) => {
</div>
)}
{viewMode === 'stack' && <StackView cards={pack.cards} />}
{viewMode === 'stack' && <StackView cards={pack.cards} cardWidth={cardWidth} />}
</div>
</div>
);

View File

@@ -4,6 +4,7 @@ import { CardHoverWrapper, FoilOverlay } from './CardPreview';
interface StackViewProps {
cards: DraftCard[];
cardWidth?: number;
}
const CATEGORY_ORDER = [
@@ -18,7 +19,7 @@ const CATEGORY_ORDER = [
'Other'
];
export const StackView: React.FC<StackViewProps> = ({ cards }) => {
export const StackView: React.FC<StackViewProps> = ({ cards, cardWidth = 150 }) => {
const categorizedCards = useMemo(() => {
const categories: Record<string, DraftCard[]> = {};
@@ -59,7 +60,7 @@ export const StackView: React.FC<StackViewProps> = ({ cards }) => {
if (catCards.length === 0) return null;
return (
<div key={category} className="flex-shrink-0 w-44 snap-start">
<div key={category} className="flex-shrink-0 snap-start" style={{ width: cardWidth }}>
{/* Header */}
<div className="flex justify-between items-center mb-2 px-1 border-b border-slate-700 pb-1">
<span className="text-xs font-bold text-slate-400 uppercase tracking-wider">{category}</span>

View File

@@ -101,6 +101,11 @@ export const CubeManager: React.FC<CubeManagerProps> = ({ packs, setPacks, onGoT
return saved ? parseInt(saved) : 3;
});
const [cardWidth, setCardWidth] = useState(() => {
const saved = localStorage.getItem('cube_cardWidth');
return saved ? parseInt(saved) : 140;
});
// --- Persistence Effects ---
useEffect(() => localStorage.setItem('cube_inputText', inputText), [inputText]);
useEffect(() => localStorage.setItem('cube_filters', JSON.stringify(filters)), [filters]);
@@ -108,6 +113,7 @@ export const CubeManager: React.FC<CubeManagerProps> = ({ packs, setPacks, onGoT
useEffect(() => localStorage.setItem('cube_sourceMode', sourceMode), [sourceMode]);
useEffect(() => localStorage.setItem('cube_selectedSets', JSON.stringify(selectedSets)), [selectedSets]);
useEffect(() => localStorage.setItem('cube_numBoxes', numBoxes.toString()), [numBoxes]);
useEffect(() => localStorage.setItem('cube_cardWidth', cardWidth.toString()), [cardWidth]);
const fileInputRef = useRef<HTMLInputElement>(null);
@@ -660,6 +666,22 @@ export const CubeManager: React.FC<CubeManagerProps> = ({ packs, setPacks, onGoT
{copySuccess ? <Check className="w-4 h-4 text-emerald-400" /> : <Copy className="w-4 h-4" />}
<span className="hidden sm:inline">{copySuccess ? 'Copied!' : 'Copy'}</span>
</button>
{/* Size Slider */}
<div className="flex items-center gap-2 bg-slate-800 rounded-lg px-2 py-1 border border-slate-700 h-9 mr-2 hidden sm:flex">
<div className="w-3 h-4 rounded border border-slate-500 bg-slate-700" title="Small Cards" />
<input
type="range"
min="100"
max="300"
step="10"
value={cardWidth}
onChange={(e) => setCardWidth(parseInt(e.target.value))}
className="w-24 accent-purple-500 cursor-pointer h-1.5 bg-slate-600 rounded-lg appearance-none"
title={`Card Size: ${cardWidth}px`}
/>
<div className="w-4 h-6 rounded border border-slate-500 bg-slate-700" title="Large Cards" />
</div>
</>
)}
@@ -679,7 +701,7 @@ export const CubeManager: React.FC<CubeManagerProps> = ({ packs, setPacks, onGoT
) : (
<div className="grid grid-cols-1 gap-6 pb-20">
{packs.map((pack) => (
<PackCard key={pack.id} pack={pack} viewMode={viewMode} />
<PackCard key={pack.id} pack={pack} viewMode={viewMode} cardWidth={cardWidth} />
))}
</div>
)}