From 4ad0cd6fdcb856b456c92f7e1671e66ab03a8797 Mon Sep 17 00:00:00 2001 From: dnviti Date: Wed, 17 Dec 2025 01:28:26 +0100 Subject: [PATCH] feat: Implement dynamic art cropping for small cards and refine preview suppression for large cards. --- docs/development/CENTRAL.md | 3 ++ .../2025-12-17-024500_dynamic_art_cropping.md | 18 +++++++ ...2-17-025000_refined_preview_suppression.md | 14 +++++ ...-17-025500_explicit_preview_suppression.md | 16 ++++++ src/client/src/components/CardPreview.tsx | 7 +-- src/client/src/components/PackCard.tsx | 51 ++++++++++--------- src/client/src/components/StackView.tsx | 6 ++- .../src/services/PackGeneratorService.ts | 2 + src/server/services/PackGeneratorService.ts | 2 + src/server/services/ScryfallService.ts | 2 +- 10 files changed, 92 insertions(+), 29 deletions(-) create mode 100644 docs/development/devlog/2025-12-17-024500_dynamic_art_cropping.md create mode 100644 docs/development/devlog/2025-12-17-025000_refined_preview_suppression.md create mode 100644 docs/development/devlog/2025-12-17-025500_explicit_preview_suppression.md diff --git a/docs/development/CENTRAL.md b/docs/development/CENTRAL.md index 8ba6aba..8e92e78 100644 --- a/docs/development/CENTRAL.md +++ b/docs/development/CENTRAL.md @@ -58,3 +58,6 @@ - [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. +- [Dynamic Art Cropping](./devlog/2025-12-17-024500_dynamic_art_cropping.md): Completed. Implemented automatic switching to full-art/art-crop images when card size is reduced below readability threshold. +- [Refined Preview Suppression](./devlog/2025-12-17-025000_refined_preview_suppression.md): Completed. Adjusted suppression threshold to 200px to better support Stack View's pop-up behavior. +- [Explicit Preview Suppression](./devlog/2025-12-17-025500_explicit_preview_suppression.md): Completed. Implemented strict `preventPreview` prop to enforce suppression logic reliably regardless of card overlap or DOM state. diff --git a/docs/development/devlog/2025-12-17-024500_dynamic_art_cropping.md b/docs/development/devlog/2025-12-17-024500_dynamic_art_cropping.md new file mode 100644 index 0000000..eab0a3a --- /dev/null +++ b/docs/development/devlog/2025-12-17-024500_dynamic_art_cropping.md @@ -0,0 +1,18 @@ +# Dynamic Art Cropping + +## Objective +Automatically switch card visualizations to "Full Art" (Art Crop) mode when the thumbnail size is reduced below a readability threshold, maximizing the visual impact of the artwork when text is too small to read. + +## Changes +- **Backend (Client & Server)**: + - Updated `DraftCard` interface to include `imageArtCrop`. + - Modified parsing services (`PackGeneratorService`) to extract and populate `imageArtCrop` from Scryfall data. +- **Frontend (UI)**: + - **PackCard (Grid View)**: Implemented a conditional check: if `cardWidth < 170px`, the image source switches to `imageArtCrop`. + - **StackView (Deck/Collection)**: Applied the same logic. +- **Visuals**: + - The `object-cover` CSS property ensures the rectangular art crop fills the entire card frame, creating a "borderless/full-art" look. + - The **Foil Overlay** and **Rarity Stripe** remain visible on top of the art crop, maintaining game state clarity. + +## Result +As you slide the size slider down, the cards seamlessly transform from standard cards (with borders and text) to vibrant, full-art thumbnails. This creates a stunning "mosaic" effect for the cube overview and deck stacks, solving the issue of illegible text at small scales. diff --git a/docs/development/devlog/2025-12-17-025000_refined_preview_suppression.md b/docs/development/devlog/2025-12-17-025000_refined_preview_suppression.md new file mode 100644 index 0000000..4a05119 --- /dev/null +++ b/docs/development/devlog/2025-12-17-025000_refined_preview_suppression.md @@ -0,0 +1,14 @@ +# Refined Preview Suppression + +## Objective +Tune the "Smart Preview Suppression" logic to better align with the Stack View's behavior. In Stack View, hovering a card causes it to "pop" to the front (`z-index` shift), making the card fully visible in-place. Because of this, showing a floating preview is redundant and distracting once the card is large enough to be read directly. + +## Changes +- Modified `handleMouseEnter` in `src/client/src/components/CardPreview.tsx`: + - Lowered the suppression threshold from `>240x300` to `>200x270`. + - **Logic**: + - Cards sized via the slider to be larger than **200px** wide are now considered "readable" (especially since the 'Art Crop' mode turns off at 170px, leaving a range of 170-199 where preview is explicitly ON for text, and 200+ where it's suppressed). + - This effectively disables the popup in Stack View for medium-to-large settings, relying on the native "pop-to-front" hover effect for inspection. + +## Result +A cleaner, less jittery drafting experience where large cards simply "lift up" for inspection, while smaller cards still get the helpful magnified popup. diff --git a/docs/development/devlog/2025-12-17-025500_explicit_preview_suppression.md b/docs/development/devlog/2025-12-17-025500_explicit_preview_suppression.md new file mode 100644 index 0000000..4710b50 --- /dev/null +++ b/docs/development/devlog/2025-12-17-025500_explicit_preview_suppression.md @@ -0,0 +1,16 @@ +# Explicit Preview Suppression + +## Objective +Enforce strict preview suppression when card sizes are large (`>= 200px`), regardless of element visibility, overlap, or DOM layout quirks. This ensures that in Stack View, where cards overlap, no stray previews are triggered for cards that are ostensibly "big enough" to be read directly. + +## Changes +- **CardPreview (`CardHoverWrapper`)**: + - Added an optional `preventPreview?: boolean` prop. + - Updated `handleMouseEnter` to immediately return if `preventPreview` is true, bypassing any DOM size checks that might be inaccurate for obscured elements. +- **PackCard (Grid View)**: + - Passed `preventPreview={cardWidth >= 200}` to the wrapper. +- **StackView (Stack View)**: + - Passed `preventPreview={cardWidth >= 200}` to the wrapper. + +## Result +Total consistency: if your slider is set to 200/300, floating previews are globally disabled for those views. This specifically fixes the issue where overlapping cards in a stack might have triggered previews unnecessarily. diff --git a/src/client/src/components/CardPreview.tsx b/src/client/src/components/CardPreview.tsx index 5fd49fc..ee398b1 100644 --- a/src/client/src/components/CardPreview.tsx +++ b/src/client/src/components/CardPreview.tsx @@ -83,7 +83,7 @@ export const FloatingPreview: React.FC<{ card: DraftCard; x: number; y: number; }; // --- Hover Wrapper to handle mouse events --- -export const CardHoverWrapper: React.FC<{ card: DraftCard; children: React.ReactNode; className?: string }> = ({ card, children, className }) => { +export const CardHoverWrapper: React.FC<{ card: DraftCard; children: React.ReactNode; className?: string; preventPreview?: boolean }> = ({ card, children, className, preventPreview }) => { const [isHovering, setIsHovering] = useState(false); const [isLongPressing, setIsLongPressing] = useState(false); const [renderPreview, setRenderPreview] = useState(false); @@ -127,11 +127,12 @@ export const CardHoverWrapper: React.FC<{ card: DraftCard; children: React.React const handleMouseEnter = (e: React.MouseEvent) => { if (isMobile) return; + if (preventPreview) 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) { + // Width > 200 && Height > 270 targets readable cards (Stack/Grid) but excludes list rows + if (rect.width > 200 && rect.height > 270) { return; } diff --git a/src/client/src/components/PackCard.tsx b/src/client/src/components/PackCard.tsx index 9c9fc42..b640f25 100644 --- a/src/client/src/components/PackCard.tsx +++ b/src/client/src/components/PackCard.tsx @@ -99,31 +99,36 @@ export const PackCard: React.FC = ({ pack, viewMode, cardWidth = {viewMode === 'grid' && (
- {pack.cards.map((card) => ( - -
- {/* Visual Card */} -
- {isFoil(card) && } - {isFoil(card) &&
FOIL
} + {pack.cards.map((card) => { + const useArtCrop = cardWidth < 170 && !!card.imageArtCrop; + const displayImage = useArtCrop ? card.imageArtCrop : card.image; - {card.image ? ( - {card.name} - ) : ( -
- {card.name} -
- )} - {/* Rarity Stripe */} -
+ return ( + = 200}> +
+ {/* Visual Card */} +
+ {isFoil(card) && } + {isFoil(card) &&
FOIL
} + + {displayImage ? ( + {card.name} + ) : ( +
+ {card.name} +
+ )} + {/* Rarity Stripe */} +
+
-
-
- ))} + + ); + })}
)} diff --git a/src/client/src/components/StackView.tsx b/src/client/src/components/StackView.tsx index 4351292..857d4e7 100644 --- a/src/client/src/components/StackView.tsx +++ b/src/client/src/components/StackView.tsx @@ -73,9 +73,11 @@ export const StackView: React.FC = ({ cards, cardWidth = 150 }) // Margin calculation: Negative margin to pull up next cards. // To show a "strip" of say 35px at the top of each card. const isLast = index === catCards.length - 1; + const useArtCrop = cardWidth < 170 && !!card.imageArtCrop; + const displayImage = useArtCrop ? card.imageArtCrop : card.image; return ( - + = 200}>
= ({ cards, cardWidth = 150 }) aspectRatio: '2.5/3.5' }} > - {card.name} + {card.name} {/* Optional: Shine effect for foils if visible? */} {card.finish === 'foil' && }
diff --git a/src/client/src/services/PackGeneratorService.ts b/src/client/src/services/PackGeneratorService.ts index 3c4e820..34f99c9 100644 --- a/src/client/src/services/PackGeneratorService.ts +++ b/src/client/src/services/PackGeneratorService.ts @@ -9,6 +9,7 @@ export interface DraftCard { layout?: string; // Add layout colors: string[]; image: string; + imageArtCrop?: string; set: string; setCode: string; setType: string; @@ -107,6 +108,7 @@ export class PackGeneratorService { image: useLocalImages ? `${window.location.origin}/cards/images/${cardData.set}/${cardData.id}.jpg` : (cardData.image_uris?.normal || cardData.card_faces?.[0]?.image_uris?.normal || ''), + imageArtCrop: cardData.image_uris?.art_crop || cardData.card_faces?.[0]?.image_uris?.art_crop || '', set: cardData.set_name, setCode: cardData.set, setType: setType, diff --git a/src/server/services/PackGeneratorService.ts b/src/server/services/PackGeneratorService.ts index e101222..1833234 100644 --- a/src/server/services/PackGeneratorService.ts +++ b/src/server/services/PackGeneratorService.ts @@ -10,6 +10,7 @@ export interface DraftCard { layout?: string; colors: string[]; image: string; + imageArtCrop?: string; set: string; setCode: string; setType: string; @@ -95,6 +96,7 @@ export class PackGeneratorService { layout: layout, colors: cardData.colors || [], image: cardData.image_uris?.normal || cardData.card_faces?.[0]?.image_uris?.normal || '', + imageArtCrop: cardData.image_uris?.art_crop || cardData.card_faces?.[0]?.image_uris?.art_crop || '', set: cardData.set_name, setCode: cardData.set, setType: setType, diff --git a/src/server/services/ScryfallService.ts b/src/server/services/ScryfallService.ts index a08afa9..5584148 100644 --- a/src/server/services/ScryfallService.ts +++ b/src/server/services/ScryfallService.ts @@ -31,7 +31,7 @@ export interface ScryfallCard { image_uris?: { normal: string; small?: string; large?: string; png?: string; art_crop?: string; border_crop?: string }; card_faces?: { name: string; - image_uris?: { normal: string; }; + image_uris?: { normal: string; art_crop?: string; }; type_line?: string; mana_cost?: string; oracle_text?: string;