File size: 9,849 Bytes
7c367a8
6ff55c7
f7cecf3
 
2dee593
f5e80db
 
 
 
 
 
13fe1f0
 
 
f5e80db
3ed93f4
13fe1f0
 
f5e80db
ed83748
 
2dee593
3ed93f4
13fe1f0
 
1d9a597
3c8b4ae
f5e80db
 
89e5e47
6ff55c7
f7cecf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ff55c7
 
14b6654
 
 
 
 
 
 
 
 
 
 
d9f8dc1
 
 
 
6ff55c7
 
a7d5331
80ac556
a7d5331
6ff55c7
a7edf74
 
 
 
 
 
 
6ff55c7
80ac556
a7d5331
 
 
 
 
 
80ac556
 
 
a7d5331
6ff55c7
2dee593
6ff55c7
1d9a597
 
 
2dee593
 
6ff55c7
1d9a597
 
 
 
 
 
 
 
 
 
6ce87f4
1d9a597
2dee593
6ff55c7
f7cecf3
6ff55c7
 
14b6654
6ff55c7
14b6654
 
 
6e5220d
14b6654
 
 
 
 
 
 
 
 
 
 
 
 
1d9a597
6ff55c7
1d9a597
 
 
 
 
 
 
f7cecf3
 
6ff55c7
1d9a597
6ff55c7
1d9a597
2b58a06
2dee593
1d9a597
 
 
2b58a06
1d9a597
6ff55c7
 
 
 
2dee593
 
1d9a597
 
2dee593
6ff55c7
 
 
 
 
 
 
 
 
 
 
 
 
d9f8dc1
6ff55c7
 
 
 
 
 
 
 
 
1d9a597
f7cecf3
 
 
6ff55c7
2b58a06
6ff55c7
 
 
 
 
 
2b58a06
6ff55c7
1d9a597
 
 
 
 
 
 
 
 
 
 
6ff55c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9f8dc1
6ff55c7
 
 
 
 
 
f7cecf3
2b58a06
f7cecf3
 
f5e80db
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// src/components/PitchView.jsx
import React, { useRef, useState, useEffect, useCallback } from "react";
import { DraggablePlayer } from "./DraggablePlayer";

/*
  DESIGN DIMENSIONS β€” single source of truth for the pitch.
  All cards are laid out at these fixed pixel dimensions and the whole pitch
  is uniformly transform-scaled to fit the actual container. This keeps
  proportions identical across every screen size (no element gets relatively
  bigger or smaller than another when the viewport changes).

  Card: 88Γ—106px (larger for better legibility)
  Label strip: ~52px
  Card slot total β‰ˆ 158px per row

  DESIGN_WIDTH = 670: fits widest row (5 mids/defs) = 5Γ—88 + 4Γ—24 = 536 ≀ 670 βœ“
  4 starter rows + gaps β‰ˆ 780px total height
  Bench β‰ˆ 230px

  MAX_SCALE > 1 lets the pitch scale modestly on wide screens without badges
  and labels becoming disproportionately large.
*/
const DESIGN_WIDTH = 670;
const STARTERS_H   = 780;
const BENCH_H      = 230;
// Gap between player card wrappers in px (in design space)
const CARD_GAP     = 24;
// Allow the pitch to scale up to 1.1Γ— on wide screens so cards don't look
// undersized on desktop. Below DESIGN_WIDTH it scales down naturally to fit.
const MAX_SCALE    = 1.0;

export const PitchView = ({
  teamData,
  activeDragPlayer,
  isValidSwap,
  captainId,
  viceId,
  handleCapChange,
  playerCardGWs,
  fixtures,
  activeGW,
  setSelectedPlayer,
  handleUndoTransfer,
  highlightTransferIds,
  solverTransferPairs,
  resetHighlightedTransfer,
  chipsByGw,
}) => {
  const containerRef = useRef(null);
  const [scale, setScale] = useState(1);
  const [isFullscreen, setIsFullscreen] = useState(false);

  const toggleFullscreen = useCallback(() => {
    if (!containerRef.current) return;
    if (!document.fullscreenElement) {
      containerRef.current.requestFullscreen?.();
    } else {
      document.exitFullscreen?.();
    }
  }, []);

  const handlePlayerClick = useCallback((player) => {
    setSelectedPlayer(player);
  }, [setSelectedPlayer]);

  const updateScale = useCallback(() => {
    if (!containerRef.current) return;
    const next = Math.min(MAX_SCALE, containerRef.current.offsetWidth / DESIGN_WIDTH);
    setScale(prev => Math.abs(prev - next) > 0.005 ? next : prev);
  }, []);

  useEffect(() => {
    updateScale();
    const ro = new ResizeObserver(updateScale);
    if (containerRef.current) ro.observe(containerRef.current);
    return () => ro.disconnect();
  }, [updateScale]);

  useEffect(() => {
    const handler = () => {
      const fs = !!document.fullscreenElement;
      setIsFullscreen(fs);
      if (!containerRef.current) return;
      const w = fs ? window.innerWidth : containerRef.current.offsetWidth;
      const next = Math.min(MAX_SCALE, w / DESIGN_WIDTH);
      setScale(prev => Math.abs(prev - next) > 0.005 ? next : prev);
    };
    document.addEventListener("fullscreenchange", handler);
    return () => document.removeEventListener("fullscreenchange", handler);
  }, []);

  const isBBChip = chipsByGw[activeGW] === "bb";

  // The outer "height-reserving" divs must use the SCALED height so the
  // document flow collapses correctly β€” otherwise content below (bench, etc.)
  // overlaps the starters area.
  const scaledStartersH = STARTERS_H * scale;
  const scaledBenchH    = BENCH_H    * scale;

  // Inner transform div: fixed at design size, scaled from top-center
  const innerStyle = (designH, extraStyle = {}) => ({
    width:           DESIGN_WIDTH,
    height:          designH,
    transform:       `scale(${scale})`,
    transformOrigin: "top center",
    position:        "absolute",
    left:            "50%",
    marginLeft:      -(DESIGN_WIDTH / 2),
    top:             0,
    willChange:     "transform",
    ...extraStyle,
  });

  return (
    <div
      ref={containerRef}
      className="fullscreen-pitch w-full bg-[#0a3a2a] rounded-2xl border-4 border-[#072a1e] relative flex flex-col shadow-[0_0_50px_rgba(0,0,0,0.5)] isolate"
    >
      {/* Fullscreen toggle β€” mobile only */}
      <button
        onClick={toggleFullscreen}
        className="small-touch-target absolute top-2 right-2 z-50 md:hidden p-1.5 bg-black/40 hover:bg-black/60 rounded-lg border border-white/20 text-white/70 hover:text-white transition-colors"
      >
        {isFullscreen ? (
          <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
            <path d="M8 3v3a2 2 0 0 1-2 2H3"/><path d="M21 8h-3a2 2 0 0 1-2-2V3"/>
            <path d="M3 16h3a2 2 0 0 1 2 2v3"/><path d="M16 21v-3a2 2 0 0 1 2-2h3"/>
          </svg>
        ) : (
          <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
            <path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/>
            <path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/>
          </svg>
        )}
      </button>
      {/* ── PITCH LINES β€” fill the full container, never scaled ── */}
      <div className="absolute inset-0 pointer-events-none opacity-30">
        <div className="absolute top-0 w-full h-1/2 border-b-2 border-white/40" />
        <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-48 h-48 border-2 border-white/40 rounded-full" />
        <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-2 h-2 bg-white/40 rounded-full" />
        <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[50%] h-[15%] border-2 border-white/40 border-t-0" />
        <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[20%] h-[5%] border-2 border-white/40 border-t-0" />
        <div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[50%] h-[15%] border-2 border-white/40 border-b-0" />
        <div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[20%] h-[5%] border-2 border-white/40 border-b-0" />
      </div>

      {/* ── STARTERS ── */}
      {/* Outer reserves scaled height in document flow */}
      <div style={{ height: scaledStartersH }} className="relative w-full">
        {/* Inner is at design dimensions, scaled from top-center */}
        <div
          style={innerStyle(STARTERS_H)}
          className="flex flex-col justify-evenly z-10"
          // py-6 = 24px top + 24px bottom padding (48px total)
          // justify-evenly distributes remaining 692px across 4 rows of 152px + 5 gaps
        >
          <div style={{ height: 24 }} /> {/* top padding */}
          {["G", "D", "M", "F"].map((pos) => {
            const rowPlayers = teamData.slice(0, 11).filter((p) => p.Pos === pos);
            if (rowPlayers.length === 0) return null;
            return (
              <div
                key={pos}
                className="flex justify-center items-start w-full flex-1"
                style={{ gap: CARD_GAP }}
              >
                {rowPlayers.map((p) => (
                  <DraggablePlayer
                    key={p.ID}
                    player={p}
                    isBench={false}
                    isActiveDrag={activeDragPlayer !== null}
                    isValidTarget={isValidSwap(activeDragPlayer, p)}
                    captainId={captainId}
                    viceId={viceId}
                    handleCapChange={handleCapChange}
                    playerCardGWs={playerCardGWs}
                    fixtures={fixtures}
                    activeGW={activeGW}
                    onPlayerClick={handlePlayerClick}
                    onUndo={handleUndoTransfer}
                    isHighlighted={Array.from(highlightTransferIds[activeGW] || []).includes(p.ID)}
                    onSolverUndo={(solverTransferPairs[activeGW] || {})[p.ID] ? () => resetHighlightedTransfer(p) : undefined}
                    activeChipType={chipsByGw[activeGW]}
                  />
                ))}
              </div>
            );
          })}
          <div style={{ height: 24 }} /> {/* bottom padding */}
        </div>
      </div>

      {/* ── BENCH ── */}
      <div
        className={`w-full border-t-2 z-10 transition-colors duration-300 relative ${
          isBBChip
            ? "bg-emerald-950/60 border-emerald-500/50 shadow-[0_0_24px_rgba(16,185,129,0.2)]"
            : "bg-slate-950/90 border-slate-800"
        }`}
        style={{ height: scaledBenchH }}
      >
        <div
          style={innerStyle(BENCH_H, {
            display:        "flex",
            justifyContent: "center",
            alignItems:     "flex-start",
            gap:            CARD_GAP,
            paddingTop:     16,
            paddingBottom:  32,
            paddingLeft:    24,
            paddingRight:   24,
            boxSizing:      "border-box",
          })}
        >
          {teamData.slice(11, 15).map((p, benchIndex) => (
            <DraggablePlayer
              key={p.ID}
              player={p}
              isBench={true}
              benchIndex={benchIndex}
              isActiveDrag={activeDragPlayer !== null}
              isValidTarget={isValidSwap(activeDragPlayer, p)}
              captainId={captainId}
              viceId={viceId}
              handleCapChange={handleCapChange}
              playerCardGWs={playerCardGWs}
              fixtures={fixtures}
              activeGW={activeGW}
              onPlayerClick={handlePlayerClick}
              onUndo={handleUndoTransfer}
              isHighlighted={Array.from(highlightTransferIds[activeGW] || []).includes(p.ID)}
              onSolverUndo={(solverTransferPairs[activeGW] || {})[p.ID] ? () => resetHighlightedTransfer(p) : undefined}
              activeChipType={chipsByGw[activeGW]}
            />
          ))}
        </div>
      </div>
    </div>
  );
};