import React, { useContext, memo } from "react"; import { Plus, RotateCcw } from "lucide-react"; import { getShortName } from "../utils/teams"; import { getPlayerPrice } from "../utils/fplLogic"; import { PlayerContext, FixturesContext } from "../PlayerContext"; // Fixed card dimensions — these are design-space pixels (before PitchView scaling). // Sized to be prominent and legible on desktop, scaling gracefully on mobile via // the PitchView scale transform. Keeps proportions consistent across devices. const CARD_W = 108; const CARD_H = 108; // photo card height only (label strip is separate, in flow below) // Badge sizing — using percentage of card width ensures badges scale proportionally // with the card rather than being fixed pixels that iOS may try to inflate. // 15% of 88px = ~13px at design scale — large enough to read and tap comfortably. const BADGE_PCT = 0.25; export const PlayerCardVisual = ({ player, isBench, captainId, viceId, handleCapChange, playerCardGWs, fixtures, activeGW, onPlayerClick, onUndo, onSolverUndo, activeChipType, }) => { if (player.isBlank) { return (
onPlayerClick(player)} style={{ width: CARD_W, height: CARD_H }} className="relative flex flex-col items-center justify-center cursor-pointer border-2 border-dashed border-slate-500 bg-slate-900/60 rounded-xl hover:bg-slate-800 hover:border-emerald-400 transition-all z-20 shadow-inner group" > {player.replacedPlayer && (
e.stopPropagation()} onClick={(e) => onUndo(e, player.ID, player.replacedPlayer)} style={{ width: CARD_W * BADGE_PCT, height: CARD_W * BADGE_PCT, minWidth: 0, minHeight: 0, touchAction: "manipulation", }} className="flex items-center justify-center rounded-full bg-red-600 hover:bg-red-500 text-white transition-colors border border-red-400 shadow-lg cursor-pointer select-none" title="Undo transfer" >
)} {player.Pos}
{/* Placeholder label area so blank slots have identical height to real cards */}
); } const isCap = player.ID === captainId; const isVice = player.ID === viceId; const photoUrl = player.photo ? `https://wsrv.nl/?url=resources.premierleague.com/premierleague25/photos/players/110x140/${player.photo.replace(".jpg", ".png")}&output=webp&w=110&q=75` : ""; const effectiveFixtures = useContext(FixturesContext); const TEAM_MAP = { "Arsenal": 1, "Aston Villa": 2, "Burnley": 3, "Bournemouth": 4, "AFC Bournemouth": 4, "Brentford": 5, "Brighton": 6, "Brighton and Hove Albion": 6, "Chelsea": 7, "Crystal Palace": 8, "Everton": 9, "Fulham": 10, "Leeds": 11, "Leeds United": 11, "Liverpool": 12, "Man City": 13, "Manchester City": 13, "Man Utd": 14, "Manchester United": 14, "Newcastle": 15, "Newcastle United": 15, "Nott'm Forest": 16, "Nottingham Forest": 16, "Sunderland": 17, "Spurs": 18, "Tottenham": 18, "Tottenham Hotspur": 18, "West Ham": 19, "West Ham United": 19, "Wolves": 20, "Wolverhampton Wanderers": 20 }; const getActiveMatches = (teamName, gw) => { if (!fixtures || !fixtures.length || !gw) return []; const activeMatches = []; fixtures.forEach(m => { if (m.home_team !== teamName && m.away_team !== teamName) return; const hId = m.home_team_id || TEAM_MAP[m.home_team] || m.home_team; const aId = m.away_team_id || TEAM_MAP[m.away_team] || m.away_team; const matchId = `${hId}_vs_${aId}`; const override = effectiveFixtures?.[matchId]; if (override) { if (Number(override[gw]) >= 0.01) activeMatches.push({ ...m, prob: Number(override[gw]) }); } else if (String(m.GW) === String(gw)) { activeMatches.push({ ...m, prob: 1.0 }); } }); return activeMatches; }; const currentGwMatches = getActiveMatches(player.Team, activeGW); const isBlankThisGw = currentGwMatches.length === 0; const renderFixtures = (teamName, gw) => { const activeMatches = gw === activeGW ? currentGwMatches : getActiveMatches(teamName, gw); if (activeMatches.length === 0) { return BLANK; } return activeMatches.map((m, idx) => { const isHome = m.home_team === teamName; const oppName = getShortName(isHome ? m.away_team : m.home_team); const loc = isHome ? "H" : "A"; const isGhost = m.prob < 1; return ( {oppName} ({loc}) {isGhost && ( {Math.round(m.prob * 100)}% )} {idx < activeMatches.length - 1 && ( )} ); }); }; // EV text sizes — primary GW gets the strongest visual weight, subsequent // GWs taper down. Sized for comfortable reading at the larger card scale. const evStyles = [ "text-emerald-400 text-[13px] font-extrabold", "text-emerald-500 text-[11px] font-bold", "text-emerald-600 text-[9px] font-semibold", ]; const isTransferIn = Boolean(player.replacedPlayer || onSolverUndo); return ( /* OUTER WRAPPER — this is what the row gap acts on. Width is exactly CARD_W so the gap math in PitchView is predictable. The wrapper is a flex-col: [photo card] then [label strip]. */
onPlayerClick(player)} className="relative flex flex-col items-center cursor-grab" style={{ width: CARD_W }} > {/* ── PHOTO CARD ── fixed size, photo is % of this div specifically */}
{/* C / V / Reset stack. Key fixes for mobile responsiveness: 1. Use
instead of
); }; export default memo(PlayerCardVisual);