👁️
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor card image to fix bug introduced by trying to fix mobile layout

+88 -42
+29 -15
src/components/CardImage.tsx
··· 22 22 rgba(0,0,0,0.05) 16px 23 23 )`; 24 24 25 + /** Scryfall card image aspect ratio (672×936 pixels) */ 26 + export const CARD_ASPECT_RATIO = "672/936"; 27 + 28 + /** Card border radius matching physical card corners */ 29 + export const CARD_BORDER_RADIUS = "4.75%/3.5%"; 30 + 25 31 interface CardImageProps { 26 32 card: Pick<Card, "name" | "id"> & { layout?: Layout }; 27 33 size?: ImageSize; 28 34 face?: CardFaceType; 29 - className?: string; 35 + /** Classes for the outer wrapper (sizing, layout). For non-flippable cards, applied to img. */ 36 + outerClassName?: string; 37 + /** Classes for the img element (shadows, object-fit, etc.) */ 38 + imgClassName?: string; 30 39 isFlipped?: boolean; 31 40 onFlip?: (flipped: boolean) => void; 32 41 } ··· 46 55 card, 47 56 size = "normal", 48 57 face = "front", 49 - className, 58 + outerClassName, 59 + imgClassName, 50 60 isFlipped: controlledFlipped, 51 61 onFlip, 52 62 }: CardImageProps) { ··· 78 88 } 79 89 }; 80 90 81 - const baseClassName = `${className ?? ""} rounded-[4.75%/3.5%] aspect-[5/7]`; 91 + // Base styles always applied to images 92 + const imgBaseClassName = `rounded-[${CARD_BORDER_RADIUS}] aspect-[${CARD_ASPECT_RATIO}] max-w-full ${imgClassName ?? ""}`; 82 93 83 94 if (!flippable) { 95 + // Non-flippable: no wrapper, both outer and img classes go on the img 84 96 return ( 85 97 <img 86 98 src={getImageUri(card.id, size, face)} 87 99 alt={card.name} 88 - className={`${baseClassName} bg-gray-200 dark:bg-zinc-700`} 100 + className={`${outerClassName ?? ""} ${imgBaseClassName} bg-gray-200 dark:bg-zinc-700`} 89 101 style={{ backgroundImage: PLACEHOLDER_STRIPES }} 90 102 loading="lazy" 91 103 /> ··· 96 108 const rotateScale = 5 / 7; 97 109 98 110 return ( 99 - <div className="relative group"> 111 + <div 112 + className={`relative group aspect-[${CARD_ASPECT_RATIO}] ${outerClassName ?? ""}`} 113 + > 100 114 {flipBehavior === "transform" && hasBack ? ( 101 115 <div 102 116 className="w-full motion-safe:transition-transform motion-safe:duration-500 motion-safe:ease-in-out" ··· 108 122 <img 109 123 src={getImageUri(card.id, size, "front")} 110 124 alt={card.name} 111 - className={`${baseClassName} bg-gray-200 dark:bg-zinc-700`} 125 + className={`${imgBaseClassName} bg-gray-200 dark:bg-zinc-700`} 112 126 loading="lazy" 113 127 style={{ 114 128 backfaceVisibility: "hidden", ··· 118 132 <img 119 133 src={getImageUri(card.id, size, "back")} 120 134 alt={`${card.name} (back)`} 121 - className={`${baseClassName} bg-gray-200 dark:bg-zinc-700 absolute inset-0`} 135 + className={`${imgBaseClassName} bg-gray-200 dark:bg-zinc-700 absolute inset-0`} 122 136 loading="lazy" 123 137 style={{ 124 138 backfaceVisibility: "hidden", ··· 131 145 <img 132 146 src={getImageUri(card.id, size, face)} 133 147 alt={card.name} 134 - className={`${baseClassName} bg-gray-200 dark:bg-zinc-700 motion-safe:transition-transform motion-safe:duration-500 motion-safe:ease-in-out`} 148 + className={`${imgBaseClassName} bg-gray-200 dark:bg-zinc-700 motion-safe:transition-transform motion-safe:duration-500 motion-safe:ease-in-out`} 135 149 loading="lazy" 136 150 style={{ 137 151 backgroundImage: PLACEHOLDER_STRIPES, ··· 162 176 export function CardSkeleton({ className }: { className?: string }) { 163 177 return ( 164 178 <div 165 - className={`aspect-[5/7] rounded-[4.75%/3.5%] bg-gray-200 dark:bg-zinc-700 animate-pulse ${className ?? ""}`} 179 + className={`aspect-[${CARD_ASPECT_RATIO}] rounded-[${CARD_BORDER_RADIUS}] bg-gray-200 dark:bg-zinc-700 animate-pulse ${className ?? ""}`} 166 180 style={{ backgroundImage: PLACEHOLDER_STRIPES }} 167 181 /> 168 182 ); ··· 183 197 <CardImage 184 198 card={card} 185 199 size="normal" 186 - className="w-full h-full object-cover rounded-[4.75%/3.5%]" 200 + outerClassName="w-full h-full" 201 + imgClassName="object-cover" 187 202 /> 188 203 <div className="absolute inset-0 bg-gradient-to-t from-black/80 dark:from-black/90 via-black/0 to-black/0 opacity-0 group-hover:opacity-100 motion-safe:transition-opacity"> 189 204 <div className="absolute bottom-0 left-0 right-0 p-3"> ··· 200 215 </> 201 216 ); 202 217 203 - const className = 204 - "group relative aspect-[5/7] overflow-hidden hover:ring-2 hover:ring-cyan-500 motion-safe:transition-shadow block rounded-[4.75%/3.5%]"; 218 + const className = `group relative aspect-[${CARD_ASPECT_RATIO}] overflow-hidden hover:ring-2 hover:ring-cyan-500 motion-safe:transition-shadow block rounded-[${CARD_BORDER_RADIUS}]`; 205 219 206 220 if (href) { 207 221 return ( ··· 245 259 <CardImage 246 260 card={card} 247 261 size="normal" 248 - className="w-full h-full object-cover rounded-[4.75%/3.5%]" 262 + outerClassName="w-full h-full" 263 + imgClassName="object-cover" 249 264 /> 250 265 ); 251 266 252 - const baseClassName = 253 - "aspect-[5/7] overflow-hidden hover:ring-2 hover:ring-cyan-500 motion-safe:transition-shadow block rounded-[4.75%/3.5%]"; 267 + const baseClassName = `aspect-[${CARD_ASPECT_RATIO}] overflow-hidden hover:ring-2 hover:ring-cyan-500 motion-safe:transition-shadow block rounded-[${CARD_BORDER_RADIUS}]`; 254 268 const finalClassName = `${baseClassName} ${className ?? ""}`; 255 269 256 270 if (href) {
+1 -5
src/components/CardSpread.tsx
··· 73 73 } as React.CSSProperties 74 74 } 75 75 > 76 - <CardImage 77 - card={{ id: id as ScryfallId, name: "" }} 78 - size="small" 79 - className="rounded" 80 - /> 76 + <CardImage card={{ id: id as ScryfallId, name: "" }} size="small" /> 81 77 </div> 82 78 ))} 83 79 <style>{`
+2 -1
src/components/HoverCardPreview.tsx
··· 127 127 <CardImage 128 128 card={card} 129 129 size="normal" 130 - className="w-full shadow-2xl shadow-black/50" 130 + outerClassName="w-full" 131 + imgClassName="shadow-2xl shadow-black/50" 131 132 /> 132 133 </div>, 133 134 document.body,
+2 -1
src/components/deck/CardModal.tsx
··· 173 173 <CardImage 174 174 card={cardData} 175 175 size="normal" 176 - className="w-full h-auto shadow-lg rounded-[4.75%/3.5%]" 176 + outerClassName="w-full h-auto" 177 + imgClassName="shadow-lg" 177 178 /> 178 179 </div> 179 180 </div>
+2 -1
src/components/deck/CardPreviewPane.tsx
··· 16 16 <CardImage 17 17 card={data} 18 18 size="large" 19 - className="shadow-[0_0.5rem_1.875rem_rgba(0,0,0,0.4)] dark:shadow-[0_0.5rem_1.875rem_rgba(0,0,0,0.8)] w-full h-auto object-contain rounded-[4.75%/3.5%]" 19 + outerClassName="w-full h-auto" 20 + imgClassName="shadow-[0_0.5rem_1.875rem_rgba(0,0,0,0.4)] dark:shadow-[0_0.5rem_1.875rem_rgba(0,0,0,0.8)] object-contain" 20 21 /> 21 22 )} 22 23 </div>
+1 -1
src/components/deck/DeckSampleView.tsx
··· 141 141 <CardImage 142 142 card={{ id: card.cardId, name: "" }} 143 143 size="normal" 144 - className="h-52 aspect-[5/7] rounded-lg" 144 + outerClassName="h-52" 145 145 /> 146 146 </div> 147 147 ))}
+9 -3
src/components/deck/GoldfishDragDropProvider.tsx
··· 12 12 useSensors, 13 13 } from "@dnd-kit/core"; 14 14 import { type ReactNode, useId, useRef, useState } from "react"; 15 - import { PLACEHOLDER_STRIPES } from "@/components/CardImage"; 15 + import { 16 + CARD_ASPECT_RATIO, 17 + CARD_BORDER_RADIUS, 18 + PLACEHOLDER_STRIPES, 19 + } from "@/components/CardImage"; 16 20 import type { CardInstance } from "@/lib/goldfish/types"; 17 21 import { getImageUri } from "@/lib/scryfall-utils"; 18 22 ··· 116 120 <img 117 121 src={imageSrc} 118 122 alt="Dragging card" 119 - className="h-40 aspect-[5/7] rounded-[4.75%/3.5%] bg-gray-200 dark:bg-zinc-700 shadow-2xl" 123 + className={`h-40 aspect-[${CARD_ASPECT_RATIO}] rounded-[${CARD_BORDER_RADIUS}] bg-gray-200 dark:bg-zinc-700 shadow-2xl`} 120 124 style={{ backgroundImage: PLACEHOLDER_STRIPES }} 121 125 draggable={false} 122 126 /> 123 127 ) : ( 124 - <div className="h-40 aspect-[5/7] rounded-[4.75%/3.5%] bg-amber-700 shadow-2xl" /> 128 + <div 129 + className={`h-40 aspect-[${CARD_ASPECT_RATIO}] rounded-[${CARD_BORDER_RADIUS}] bg-amber-700 shadow-2xl`} 130 + /> 125 131 )} 126 132 {counterEntries.length > 0 && ( 127 133 <div className="absolute bottom-1 left-1 flex flex-wrap gap-1 max-w-full">
+10 -3
src/components/deck/goldfish/GoldfishBoard.tsx
··· 1 1 import type { DragEndEvent } from "@dnd-kit/core"; 2 2 import { useCallback, useRef } from "react"; 3 - import { CardImage } from "@/components/CardImage"; 3 + import { 4 + CARD_ASPECT_RATIO, 5 + CARD_BORDER_RADIUS, 6 + CardImage, 7 + } from "@/components/CardImage"; 4 8 import type { DeckCard } from "@/lib/deck-types"; 5 9 import { useGoldfishGame } from "@/lib/goldfish"; 6 10 import type { CardInstance, Zone } from "@/lib/goldfish/types"; ··· 120 124 <CardImage 121 125 card={hoveredCardData} 122 126 size="large" 123 - className="w-full aspect-[5/7] rounded-lg shadow-lg" 127 + outerClassName="w-full" 128 + imgClassName="shadow-lg" 124 129 isFlipped={ 125 130 hoveredCard?.faceIndex ? hoveredCard.faceIndex > 0 : false 126 131 } 127 132 /> 128 133 ) : ( 129 - <div className="w-full aspect-[5/7] rounded-lg bg-gray-100 dark:bg-zinc-800 flex items-center justify-center text-gray-400 dark:text-zinc-400 text-sm"> 134 + <div 135 + className={`w-full aspect-[${CARD_ASPECT_RATIO}] rounded-[${CARD_BORDER_RADIUS}] bg-gray-100 dark:bg-zinc-800 flex items-center justify-center text-gray-400 dark:text-zinc-400 text-sm`} 136 + > 130 137 {hoveredCard?.isFaceDown ? "Face down" : "Hover a card"} 131 138 </div> 132 139 )}
+7 -3
src/components/deck/goldfish/GoldfishCard.tsx
··· 1 1 import { useDraggable } from "@dnd-kit/core"; 2 - import { PLACEHOLDER_STRIPES } from "@/components/CardImage"; 2 + import { 3 + CARD_ASPECT_RATIO, 4 + CARD_BORDER_RADIUS, 5 + PLACEHOLDER_STRIPES, 6 + } from "@/components/CardImage"; 3 7 import type { CardInstance } from "@/lib/goldfish/types"; 4 8 import type { Card } from "@/lib/scryfall-types"; 5 9 import { getImageUri } from "@/lib/scryfall-utils"; ··· 60 64 <img 61 65 src={imageSrc} 62 66 alt={card?.name ?? "Card"} 63 - className={`rounded-[4.75%/3.5%] bg-gray-200 dark:bg-zinc-700 ${sizeClass} aspect-[5/7]`} 67 + className={`rounded-[${CARD_BORDER_RADIUS}] bg-gray-200 dark:bg-zinc-700 ${sizeClass} aspect-[${CARD_ASPECT_RATIO}]`} 64 68 style={{ backgroundImage: PLACEHOLDER_STRIPES }} 65 69 draggable={false} 66 70 loading="lazy" 67 71 /> 68 72 ) : ( 69 73 <div 70 - className={`rounded-[4.75%/3.5%] bg-amber-700 ${sizeClass} aspect-[5/7]`} 74 + className={`rounded-[${CARD_BORDER_RADIUS}] bg-amber-700 ${sizeClass} aspect-[${CARD_ASPECT_RATIO}]`} 71 75 /> 72 76 )} 73 77 {counterEntries.length > 0 && (
+4 -1
src/components/deck/goldfish/GoldfishSidebar.tsx
··· 1 1 import { useDroppable } from "@dnd-kit/core"; 2 2 import { Droplet, Minus, Plus, RefreshCw, RotateCcw } from "lucide-react"; 3 + import { CARD_ASPECT_RATIO, CARD_BORDER_RADIUS } from "@/components/CardImage"; 3 4 import type { CardInstance, PlayerState } from "@/lib/goldfish/types"; 4 5 import type { Card, ScryfallId } from "@/lib/scryfall-types"; 5 6 import { GoldfishCard } from "./GoldfishCard"; ··· 69 70 fromLibrary 70 71 /> 71 72 ) : ( 72 - <div className="h-40 aspect-[5/7] rounded-[4.75%/3.5%] border-2 border-dashed border-gray-300 dark:border-zinc-600" /> 73 + <div 74 + className={`h-40 aspect-[${CARD_ASPECT_RATIO}] rounded-[${CARD_BORDER_RADIUS}] border-2 border-dashed border-gray-300 dark:border-zinc-600`} 75 + /> 73 76 )} 74 77 </div> 75 78
+20 -7
src/routes/card/$id.tsx
··· 1 1 import { useQueries, useQuery } from "@tanstack/react-query"; 2 2 import { createFileRoute, Link } from "@tanstack/react-router"; 3 3 import { useEffect, useRef, useState } from "react"; 4 - import { CardImage, CardSkeleton } from "@/components/CardImage"; 4 + import { 5 + CARD_ASPECT_RATIO, 6 + CardImage, 7 + CardSkeleton, 8 + } from "@/components/CardImage"; 5 9 import { CommentsPanel } from "@/components/comments"; 6 10 import { ManaCost } from "@/components/ManaCost"; 7 11 import { OracleText } from "@/components/OracleText"; ··· 228 232 <div className="max-w-7xl mx-auto px-6 py-8"> 229 233 <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-start"> 230 234 <div className="sticky top-0 z-10 bg-white dark:bg-zinc-900 py-4 -mx-6 px-6 lg:mx-0 lg:px-0 lg:py-0 lg:bg-transparent lg:dark:bg-transparent lg:top-8 flex justify-center lg:justify-end"> 231 - <CardSkeleton className="h-[50vh] lg:h-[80vh] shadow-[0_8px_30px_rgba(0,0,0,0.4)] dark:shadow-[0_8px_30px_rgba(0,0,0,0.8)]" /> 235 + <div 236 + className={`w-full max-w-[calc(50vh*${CARD_ASPECT_RATIO})] lg:max-w-[calc(80vh*${CARD_ASPECT_RATIO})]`} 237 + > 238 + <CardSkeleton className="h-full w-full shadow-[0_8px_30px_rgba(0,0,0,0.4)] dark:shadow-[0_8px_30px_rgba(0,0,0,0.8)]" /> 239 + </div> 232 240 </div> 233 241 <div className="space-y-6 min-w-0"> 234 242 <div className="space-y-3"> ··· 267 275 <div className="max-w-7xl mx-auto px-6 py-8"> 268 276 <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 items-start"> 269 277 <div className="sticky top-0 z-10 bg-white dark:bg-zinc-900 py-4 -mx-6 px-6 lg:mx-0 lg:px-0 lg:py-0 lg:bg-transparent lg:dark:bg-transparent lg:top-8 flex justify-center lg:justify-end"> 270 - <CardImage 271 - card={displayCard} 272 - size="large" 273 - className="shadow-[0_8px_30px_rgba(0,0,0,0.4)] dark:shadow-[0_8px_30px_rgba(0,0,0,0.8)] h-[50vh] lg:h-[80vh]" 274 - /> 278 + <div 279 + className={`w-full max-w-[calc(50vh*${CARD_ASPECT_RATIO})] lg:max-w-[calc(80vh*${CARD_ASPECT_RATIO})]`} 280 + > 281 + <CardImage 282 + card={displayCard} 283 + size="large" 284 + outerClassName="w-full" 285 + imgClassName="shadow-[0_8px_30px_rgba(0,0,0,0.4)] dark:shadow-[0_8px_30px_rgba(0,0,0,0.8)]" 286 + /> 287 + </div> 275 288 </div> 276 289 277 290 <div className="space-y-6 min-w-0">
+1 -1
src/routes/profile/$did/list/$rkey/index.tsx
··· 318 318 <CardImage 319 319 card={{ id: item.scryfallId, name: card.name }} 320 320 size="small" 321 - className="w-16 h-auto rounded" 321 + outerClassName="w-16 h-auto" 322 322 /> 323 323 324 324 <div className="flex-1 min-w-0">