Spaces:
Running
Running
Commit ·
3c8b4ae
1
Parent(s): dd42d47
updates
Browse files
frontend/src/components/DraggablePlayer.jsx
CHANGED
|
@@ -44,6 +44,8 @@ export const DraggablePlayer = ({
|
|
| 44 |
WebkitUserSelect: "none",
|
| 45 |
userSelect: "none",
|
| 46 |
WebkitTouchCallout: "none",
|
|
|
|
|
|
|
| 47 |
};
|
| 48 |
|
| 49 |
return (
|
|
@@ -55,7 +57,7 @@ export const DraggablePlayer = ({
|
|
| 55 |
style={style}
|
| 56 |
{...listeners}
|
| 57 |
{...attributes}
|
| 58 |
-
className={`touch-none select-none rounded-xl transition-[box-shadow,filter] duration-200 ${isDragging ? "cursor-grabbing" : "cursor-grab"} ${isHighlighted ? "transfer-highlight-card ring-2 ring-cyan-400/40" : ""}`}
|
| 59 |
>
|
| 60 |
<PlayerCardVisual
|
| 61 |
player={player}
|
|
|
|
| 44 |
WebkitUserSelect: "none",
|
| 45 |
userSelect: "none",
|
| 46 |
WebkitTouchCallout: "none",
|
| 47 |
+
touchAction: "none",
|
| 48 |
+
WebkitUserSelect: "none",
|
| 49 |
};
|
| 50 |
|
| 51 |
return (
|
|
|
|
| 57 |
style={style}
|
| 58 |
{...listeners}
|
| 59 |
{...attributes}
|
| 60 |
+
className={`touch-none select-none rounded-xl transition-[box-shadow,filter] duration-200 ${isDragging ? "cursor-grabbing" : "cursor-grab"} ${isHighlighted ? "transfer-highlight-card ring-2 ring-cyan-400/40" : ""} [&_*]:touch-none`}
|
| 61 |
>
|
| 62 |
<PlayerCardVisual
|
| 63 |
player={player}
|
frontend/src/components/PitchView.jsx
CHANGED
|
@@ -10,11 +10,11 @@ import { DraggablePlayer } from "./DraggablePlayer";
|
|
| 10 |
BENCH_H = 180: card(80) + label(48) + mt(1) + pt(16) + pb(35) = 180 ✓
|
| 11 |
DESIGN_WIDTH = 680: fits 5 cards × 88px + 4 gaps × 40px = 440 + 160 = 600px + 40px padding ✓
|
| 12 |
*/
|
| 13 |
-
const DESIGN_WIDTH =
|
| 14 |
const STARTERS_H = 660;
|
| 15 |
const BENCH_H = 180;
|
| 16 |
// Gap between player card wrappers in px (in design space)
|
| 17 |
-
const CARD_GAP =
|
| 18 |
|
| 19 |
export const PitchView = ({
|
| 20 |
teamData,
|
|
|
|
| 10 |
BENCH_H = 180: card(80) + label(48) + mt(1) + pt(16) + pb(35) = 180 ✓
|
| 11 |
DESIGN_WIDTH = 680: fits 5 cards × 88px + 4 gaps × 40px = 440 + 160 = 600px + 40px padding ✓
|
| 12 |
*/
|
| 13 |
+
const DESIGN_WIDTH = 520;
|
| 14 |
const STARTERS_H = 660;
|
| 15 |
const BENCH_H = 180;
|
| 16 |
// Gap between player card wrappers in px (in design space)
|
| 17 |
+
const CARD_GAP = 24;
|
| 18 |
|
| 19 |
export const PitchView = ({
|
| 20 |
teamData,
|
frontend/src/components/PlayerCardVisual.jsx
CHANGED
|
@@ -5,8 +5,8 @@ import { getPlayerPrice } from "../utils/fplLogic";
|
|
| 5 |
import { PlayerContext } from "../PlayerContext";
|
| 6 |
|
| 7 |
// Fixed card dimensions — these are design-space pixels (before PitchView scaling)
|
| 8 |
-
const CARD_W =
|
| 9 |
-
const CARD_H =
|
| 10 |
|
| 11 |
export const PlayerCardVisual = ({
|
| 12 |
player,
|
|
@@ -31,19 +31,19 @@ export const PlayerCardVisual = ({
|
|
| 31 |
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"
|
| 32 |
>
|
| 33 |
{player.replacedPlayer && (
|
| 34 |
-
<div className="absolute left-[
|
| 35 |
<button
|
| 36 |
onPointerDown={(e) => e.stopPropagation()}
|
| 37 |
onClick={(e) => onUndo(e, player.ID, player.replacedPlayer)}
|
| 38 |
-
className="w-
|
| 39 |
title="Undo transfer"
|
| 40 |
>
|
| 41 |
-
<RotateCcw size={
|
| 42 |
</button>
|
| 43 |
</div>
|
| 44 |
)}
|
| 45 |
<Plus className="text-slate-500 group-hover:text-emerald-400 transition-colors mb-1" size={20} />
|
| 46 |
-
<span className="text-[
|
| 47 |
{player.Pos}
|
| 48 |
</span>
|
| 49 |
</div>
|
|
@@ -132,7 +132,7 @@ export const PlayerCardVisual = ({
|
|
| 132 |
const evStyles = [
|
| 133 |
"text-emerald-400 text-[14px] font-extrabold",
|
| 134 |
"text-emerald-500 text-[11px] font-bold",
|
| 135 |
-
"text-emerald-600 text-[
|
| 136 |
];
|
| 137 |
|
| 138 |
const isTransferIn = Boolean(player.replacedPlayer || onSolverUndo);
|
|
@@ -159,13 +159,13 @@ export const PlayerCardVisual = ({
|
|
| 159 |
style={{ width: CARD_W, height: CARD_H }}
|
| 160 |
>
|
| 161 |
{/* C / V buttons — small by design; PitchView scale handles mobile shrinking */}
|
| 162 |
-
<div className="absolute left-[
|
| 163 |
{!isBench && handleCapChange && (
|
| 164 |
<>
|
| 165 |
<button
|
| 166 |
onPointerDown={(e) => e.stopPropagation()}
|
| 167 |
onClick={(e) => { e.stopPropagation(); handleCapChange(player.ID, "C"); }}
|
| 168 |
-
className={`w-
|
| 169 |
${isCap
|
| 170 |
? activeChipType === "tc"
|
| 171 |
? "bg-purple-500 text-white border border-purple-300 text-[7px]"
|
|
@@ -178,7 +178,7 @@ export const PlayerCardVisual = ({
|
|
| 178 |
<button
|
| 179 |
onPointerDown={(e) => e.stopPropagation()}
|
| 180 |
onClick={(e) => { e.stopPropagation(); handleCapChange(player.ID, "V"); }}
|
| 181 |
-
className={`w-
|
| 182 |
${isVice ? "bg-slate-300 text-slate-900 border border-white" : "bg-slate-900/90 text-slate-400 border border-slate-700 hover:text-white"}`}
|
| 183 |
>
|
| 184 |
V
|
|
@@ -189,17 +189,17 @@ export const PlayerCardVisual = ({
|
|
| 189 |
<button
|
| 190 |
onPointerDown={(e) => e.stopPropagation()}
|
| 191 |
onClick={(e) => { e.stopPropagation(); onSolverUndo(player); }}
|
| 192 |
-
className="w-
|
| 193 |
>
|
| 194 |
-
<RotateCcw size={
|
| 195 |
</button>
|
| 196 |
) : player.replacedPlayer ? (
|
| 197 |
<button
|
| 198 |
onPointerDown={(e) => e.stopPropagation()}
|
| 199 |
onClick={(e) => onUndo(e, player.ID, player.replacedPlayer)}
|
| 200 |
-
className="w-
|
| 201 |
>
|
| 202 |
-
<RotateCcw size={
|
| 203 |
</button>
|
| 204 |
) : null}
|
| 205 |
</div>
|
|
@@ -240,17 +240,17 @@ export const PlayerCardVisual = ({
|
|
| 240 |
className={`flex flex-col items-center z-30 pointer-events-none mt-[1px] flex-shrink-0 ${isBench ? "opacity-80" : "opacity-100"}`}
|
| 241 |
style={{ width: "110%" }} // 110% of 88px = 97px — fits in 88+40=128px slot gap
|
| 242 |
>
|
| 243 |
-
<div className={`w-full text-center py-[2px] truncate px-1 font-bold text-[
|
| 244 |
${isTransferIn ? "bg-cyan-600 text-white" : "bg-slate-950 text-slate-100 border border-slate-700"}`}>
|
| 245 |
{player.Name}
|
| 246 |
</div>
|
| 247 |
<div className="w-full bg-slate-200 border-x border-slate-700 flex justify-center items-center gap-1 py-[2px] shadow-inner">
|
| 248 |
-
<span className={`text-[
|
| 249 |
{isBlankThisGw ? "-" : (player[`${activeGW}_xMins`] ?? 90)}
|
| 250 |
<span className="text-[6px] font-bold text-slate-500 uppercase tracking-tight">xMins</span>
|
| 251 |
</span>
|
| 252 |
-
<span className="text-slate-400 font-light text-[
|
| 253 |
-
<span className="text-[
|
| 254 |
£{getPlayerPrice(player).toFixed(1)}
|
| 255 |
</span>
|
| 256 |
</div>
|
|
|
|
| 5 |
import { PlayerContext } from "../PlayerContext";
|
| 6 |
|
| 7 |
// Fixed card dimensions — these are design-space pixels (before PitchView scaling)
|
| 8 |
+
const CARD_W = 72;
|
| 9 |
+
const CARD_H = 88; // photo card height only (label strip is separate, in flow below)
|
| 10 |
|
| 11 |
export const PlayerCardVisual = ({
|
| 12 |
player,
|
|
|
|
| 31 |
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"
|
| 32 |
>
|
| 33 |
{player.replacedPlayer && (
|
| 34 |
+
<div className="absolute left-[1px] top-[1px] flex flex-col gap-[2px] z-40 pointer-events-auto">
|
| 35 |
<button
|
| 36 |
onPointerDown={(e) => e.stopPropagation()}
|
| 37 |
onClick={(e) => onUndo(e, player.ID, player.replacedPlayer)}
|
| 38 |
+
className="w-[14px] h-[14px] flex items-center justify-center rounded-full bg-red-600 hover:bg-red-500 text-white transition-colors border border-red-400 shadow-lg"
|
| 39 |
title="Undo transfer"
|
| 40 |
>
|
| 41 |
+
<RotateCcw size={8} strokeWidth={3} />
|
| 42 |
</button>
|
| 43 |
</div>
|
| 44 |
)}
|
| 45 |
<Plus className="text-slate-500 group-hover:text-emerald-400 transition-colors mb-1" size={20} />
|
| 46 |
+
<span className="text-[8px] font-black text-slate-500 group-hover:text-emerald-400 uppercase tracking-widest">
|
| 47 |
{player.Pos}
|
| 48 |
</span>
|
| 49 |
</div>
|
|
|
|
| 132 |
const evStyles = [
|
| 133 |
"text-emerald-400 text-[14px] font-extrabold",
|
| 134 |
"text-emerald-500 text-[11px] font-bold",
|
| 135 |
+
"text-emerald-600 text-[8px] font-semibold",
|
| 136 |
];
|
| 137 |
|
| 138 |
const isTransferIn = Boolean(player.replacedPlayer || onSolverUndo);
|
|
|
|
| 159 |
style={{ width: CARD_W, height: CARD_H }}
|
| 160 |
>
|
| 161 |
{/* C / V buttons — small by design; PitchView scale handles mobile shrinking */}
|
| 162 |
+
<div className="absolute left-[1px] top-[1px] flex flex-col gap-[2px] z-40 pointer-events-auto">
|
| 163 |
{!isBench && handleCapChange && (
|
| 164 |
<>
|
| 165 |
<button
|
| 166 |
onPointerDown={(e) => e.stopPropagation()}
|
| 167 |
onClick={(e) => { e.stopPropagation(); handleCapChange(player.ID, "C"); }}
|
| 168 |
+
className={`w-[14px] h-[14px] flex items-center justify-center rounded-full text-[8px] font-bold transition-colors shadow-md transform-gpu
|
| 169 |
${isCap
|
| 170 |
? activeChipType === "tc"
|
| 171 |
? "bg-purple-500 text-white border border-purple-300 text-[7px]"
|
|
|
|
| 178 |
<button
|
| 179 |
onPointerDown={(e) => e.stopPropagation()}
|
| 180 |
onClick={(e) => { e.stopPropagation(); handleCapChange(player.ID, "V"); }}
|
| 181 |
+
className={`w-[14px] h-[14px] flex items-center justify-center rounded-full text-[8px] font-bold transition-colors shadow-md
|
| 182 |
${isVice ? "bg-slate-300 text-slate-900 border border-white" : "bg-slate-900/90 text-slate-400 border border-slate-700 hover:text-white"}`}
|
| 183 |
>
|
| 184 |
V
|
|
|
|
| 189 |
<button
|
| 190 |
onPointerDown={(e) => e.stopPropagation()}
|
| 191 |
onClick={(e) => { e.stopPropagation(); onSolverUndo(player); }}
|
| 192 |
+
className="w-[14px] h-[14px] flex items-center justify-center rounded-full bg-red-600 hover:bg-red-500 text-white transform-gpu transition-colors border border-red-400"
|
| 193 |
>
|
| 194 |
+
<RotateCcw size={8} strokeWidth={3} />
|
| 195 |
</button>
|
| 196 |
) : player.replacedPlayer ? (
|
| 197 |
<button
|
| 198 |
onPointerDown={(e) => e.stopPropagation()}
|
| 199 |
onClick={(e) => onUndo(e, player.ID, player.replacedPlayer)}
|
| 200 |
+
className="w-[14px] h-[14px] flex items-center justify-center rounded-full bg-red-600 hover:bg-red-500 text-white transform-gpu transition-colors border border-red-400"
|
| 201 |
>
|
| 202 |
+
<RotateCcw size={8} strokeWidth={3} />
|
| 203 |
</button>
|
| 204 |
) : null}
|
| 205 |
</div>
|
|
|
|
| 240 |
className={`flex flex-col items-center z-30 pointer-events-none mt-[1px] flex-shrink-0 ${isBench ? "opacity-80" : "opacity-100"}`}
|
| 241 |
style={{ width: "110%" }} // 110% of 88px = 97px — fits in 88+40=128px slot gap
|
| 242 |
>
|
| 243 |
+
<div className={`w-full text-center py-[2px] truncate px-1 font-bold text-[8px] rounded-t shadow-md
|
| 244 |
${isTransferIn ? "bg-cyan-600 text-white" : "bg-slate-950 text-slate-100 border border-slate-700"}`}>
|
| 245 |
{player.Name}
|
| 246 |
</div>
|
| 247 |
<div className="w-full bg-slate-200 border-x border-slate-700 flex justify-center items-center gap-1 py-[2px] shadow-inner">
|
| 248 |
+
<span className={`text-[8px] font-black flex items-baseline gap-[2px] ${isBlankThisGw ? "text-slate-400" : "text-slate-800"}`}>
|
| 249 |
{isBlankThisGw ? "-" : (player[`${activeGW}_xMins`] ?? 90)}
|
| 250 |
<span className="text-[6px] font-bold text-slate-500 uppercase tracking-tight">xMins</span>
|
| 251 |
</span>
|
| 252 |
+
<span className="text-slate-400 font-light text-[8px]">|</span>
|
| 253 |
+
<span className="text-[8px] font-black text-emerald-700">
|
| 254 |
£{getPlayerPrice(player).toFixed(1)}
|
| 255 |
</span>
|
| 256 |
</div>
|