Skip to content

Commit 7c4b7a3

Browse files
haileyhuber8Copilot
andcommitted
Move fun fact below card, add objective task view
- Fun fact now renders outside the card, below the Next button - Objective tasks now appear in the task flow with hidden scores - 'Reveal Scores' button shows actual scores without blocking - Next Task button always works regardless of reveal state - Task counter now shows X/total across all task types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d692813 commit 7c4b7a3

1 file changed

Lines changed: 189 additions & 85 deletions

File tree

client/src/components/WatchModeScorer.tsx

Lines changed: 189 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ interface Props {
2929
}
3030

3131
export default function WatchModeScorer({ tasks, players, seasonNumber, onComplete, onCancel }: Props) {
32-
const scorableTasks = tasks.filter((t) => t.judgement !== "objective");
3332
const [currentTaskIdx, setCurrentTaskIdx] = useState(0);
3433
const [currentPlayerIdx, setCurrentPlayerIdx] = useState(0);
3534
const [allScores, setAllScores] = useState<Record<string, Record<number, Record<number, number>>>>({});
3635
// allScores[playerId][taskId][contestantId] = score
3736
const [showResults, setShowResults] = useState(false);
37+
const [revealedObjective, setRevealedObjective] = useState(false);
3838
const [funFacts, setFunFacts] = useState<FunFact[]>([]);
3939
const [shownFacts, setShownFacts] = useState<Record<string, Set<string>>>({}); // playerId -> set of shown fact texts
4040

@@ -49,30 +49,29 @@ export default function WatchModeScorer({ tasks, players, seasonNumber, onComple
4949
// Pick a relevant, non-repeated fun fact for the current player
5050
const getFactForPlayer = (playerId: string): string | null => {
5151
const seen = shownFacts[playerId] || new Set<string>();
52-
// Priority 1: facts about contestants in this season
5352
const contestantFacts = funFacts.filter(
5453
(f) => f.contestants.some((c) => seasonContestants.includes(c)) && !seen.has(f.text)
5554
);
5655
if (contestantFacts.length > 0) return contestantFacts[Math.floor(Math.random() * contestantFacts.length)].text;
57-
// Priority 2: facts about this season number
5856
const seasonFacts = funFacts.filter(
5957
(f) => f.seasons.includes(seasonNumber) && !seen.has(f.text)
6058
);
6159
if (seasonFacts.length > 0) return seasonFacts[Math.floor(Math.random() * seasonFacts.length)].text;
62-
// Priority 3: general facts (no season/contestant tags)
6360
const generalFacts = funFacts.filter(
6461
(f) => f.seasons.length === 0 && f.contestants.length === 0 && !seen.has(f.text)
6562
);
6663
if (generalFacts.length > 0) return generalFacts[Math.floor(Math.random() * generalFacts.length)].text;
6764
return null;
6865
};
6966

70-
// Memoize the current fact so it doesn't change on re-render
7167
const [currentFact, setCurrentFact] = useState<string | null>(null);
7268

7369
useEffect(() => {
7470
if (funFacts.length === 0) return;
75-
const playerId = players[currentPlayerIdx]?.id;
71+
// For objective tasks, use first player's id for fact tracking
72+
const playerId = currentTask?.judgement === "objective"
73+
? players[0]?.id
74+
: players[currentPlayerIdx]?.id;
7675
if (!playerId) return;
7776
const fact = getFactForPlayer(playerId);
7877
setCurrentFact(fact);
@@ -86,10 +85,15 @@ export default function WatchModeScorer({ tasks, players, seasonNumber, onComple
8685
// eslint-disable-next-line react-hooks/exhaustive-deps
8786
}, [currentTaskIdx, currentPlayerIdx, funFacts]);
8887

89-
const currentTask = scorableTasks[currentTaskIdx];
88+
const currentTask = tasks[currentTaskIdx];
89+
const isObjective = currentTask?.judgement === "objective";
90+
const scorableTasks = tasks.filter((t) => t.judgement !== "objective");
9091
const currentPlayer = players[currentPlayerIdx];
91-
const totalSteps = scorableTasks.length * players.length;
92-
const currentStep = currentTaskIdx * players.length + currentPlayerIdx + 1;
92+
93+
// For subjective tasks: step counting only counts subjective tasks × players
94+
const subjectiveIdx = scorableTasks.indexOf(currentTask);
95+
const totalSteps = isObjective ? null : scorableTasks.length * players.length;
96+
const currentStep = isObjective ? null : subjectiveIdx * players.length + currentPlayerIdx + 1;
9397

9498
const playerScoresForTask = allScores[currentPlayer?.id]?.[currentTask?.id] || {};
9599

@@ -111,16 +115,30 @@ export default function WatchModeScorer({ tasks, players, seasonNumber, onComple
111115
);
112116

113117
const handleNext = () => {
114-
if (currentPlayerIdx < players.length - 1) {
118+
if (isObjective) {
119+
// Objective tasks: advance to next task (no per-player rotation)
120+
setRevealedObjective(false);
121+
if (currentTaskIdx < tasks.length - 1) {
122+
setCurrentTaskIdx(currentTaskIdx + 1);
123+
setCurrentPlayerIdx(0);
124+
} else {
125+
setShowResults(true);
126+
}
127+
} else if (currentPlayerIdx < players.length - 1) {
115128
setCurrentPlayerIdx(currentPlayerIdx + 1);
116-
} else if (currentTaskIdx < scorableTasks.length - 1) {
129+
} else if (currentTaskIdx < tasks.length - 1) {
117130
setCurrentTaskIdx(currentTaskIdx + 1);
118131
setCurrentPlayerIdx(0);
119132
} else {
120133
setShowResults(true);
121134
}
122135
};
123136

137+
// Also reset reveal state when task changes
138+
useEffect(() => {
139+
setRevealedObjective(false);
140+
}, [currentTaskIdx]);
141+
124142
if (showResults) {
125143
return (
126144
<ResultsView
@@ -154,72 +172,171 @@ export default function WatchModeScorer({ tasks, players, seasonNumber, onComple
154172
);
155173
}
156174

157-
return (
158-
<div className="card">
159-
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "1rem" }}>
160-
<h2>
161-
Task {currentTaskIdx + 1}/{scorableTasks.length}
162-
</h2>
163-
<button onClick={onCancel} style={{ background: "none", border: "1px solid var(--tm-cream-dark)", color: "var(--tm-text-muted)", padding: "0.4rem 1rem", borderRadius: "6px", cursor: "pointer" }}>
164-
Cancel
165-
</button>
166-
</div>
175+
// Objective task view
176+
if (isObjective) {
177+
return (
178+
<div>
179+
<div className="card">
180+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "1rem" }}>
181+
<h2>Task {currentTaskIdx + 1}/{tasks.length}</h2>
182+
<button onClick={onCancel} style={{ background: "none", border: "1px solid var(--tm-cream-dark)", color: "var(--tm-text-muted)", padding: "0.4rem 1rem", borderRadius: "6px", cursor: "pointer" }}>
183+
Cancel
184+
</button>
185+
</div>
167186

168-
<div style={{ background: "var(--tm-cream-dark)", padding: "1rem", borderRadius: "8px", marginBottom: "1rem" }}>
169-
<p style={{ color: "var(--tm-red)", fontWeight: 700, marginBottom: "0.5rem" }}>{currentTask.name}</p>
170-
<p style={{ color: "var(--tm-text-muted)", fontSize: "0.85rem" }}>
171-
Type: {currentTask.judgement} · Step {currentStep}/{totalSteps}
172-
</p>
173-
</div>
187+
<div style={{ background: "var(--tm-cream-dark)", padding: "1rem", borderRadius: "8px", marginBottom: "1rem" }}>
188+
<p style={{ color: "var(--tm-red)", fontWeight: 700, marginBottom: "0.5rem" }}>{currentTask.name}</p>
189+
<p style={{ color: "var(--tm-text-muted)", fontSize: "0.85rem" }}>
190+
Type: objective — scores are determined by measurable results
191+
</p>
192+
</div>
174193

175-
<div style={{
176-
background: `${currentPlayer.color}20`,
177-
border: `2px solid ${currentPlayer.color}`,
178-
borderRadius: "8px",
179-
padding: "1rem",
180-
marginBottom: "1rem",
181-
textAlign: "center",
182-
}}>
183-
<p style={{ fontSize: "1.2rem", fontWeight: 700 }}>
184-
🎯 {currentPlayer.name}, score this task!
185-
</p>
186-
<p style={{ color: "var(--tm-text-muted)", fontSize: "0.85rem" }}>
187-
Award 1-5 points to each contestant
188-
</p>
189-
</div>
194+
<div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
195+
{currentTask.contestants.map((c) => (
196+
<div key={c.id} style={{
197+
display: "flex",
198+
alignItems: "center",
199+
justifyContent: "space-between",
200+
background: "var(--tm-cream-dark)",
201+
padding: "0.5rem 1rem",
202+
borderRadius: "6px",
203+
}}>
204+
<span style={{ fontWeight: 600 }}>{c.name}</span>
205+
<span style={{ fontWeight: 700, fontSize: "1.1rem" }}>
206+
{revealedObjective ? `${c.actualScore} pts` : "? ? ?"}
207+
</span>
208+
</div>
209+
))}
210+
</div>
190211

191-
<div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
192-
{currentTask.contestants.map((c) => (
193-
<div key={c.id} style={{
194-
display: "flex",
195-
alignItems: "center",
196-
justifyContent: "space-between",
197-
background: "var(--tm-cream-dark)",
212+
{!revealedObjective && (
213+
<button
214+
onClick={() => setRevealedObjective(true)}
215+
style={{
216+
width: "100%",
217+
marginTop: "1rem",
218+
padding: "0.75rem",
219+
background: "none",
220+
border: "1px solid var(--tm-cream-dark)",
221+
color: "var(--tm-text-muted)",
222+
borderRadius: "8px",
223+
cursor: "pointer",
224+
fontSize: "0.95rem",
225+
}}
226+
>
227+
👀 Reveal Scores
228+
</button>
229+
)}
230+
231+
<button
232+
className="btn-primary"
233+
style={{ width: "100%", marginTop: "0.5rem", padding: "0.75rem" }}
234+
onClick={handleNext}
235+
>
236+
{currentTaskIdx === tasks.length - 1 ? "🏆 See Results!" : "Next Task →"}
237+
</button>
238+
</div>
239+
240+
{currentFact && (
241+
<div style={{
242+
background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)",
243+
border: "1px solid #334155",
244+
borderRadius: "8px",
245+
padding: "0.75rem 1rem",
246+
marginTop: "1rem",
198247
}}>
199-
<span style={{ fontWeight: 600 }}>{c.name}</span>
200-
<div style={{ display: "flex", gap: "0.25rem" }}>
201-
{[1, 2, 3, 4, 5].map((score) => (
202-
<button
203-
key={score}
204-
onClick={() => setScore(c.id, score)}
205-
style={{
206-
width: "40px",
207-
height: "40px",
208-
borderRadius: "8px",
209-
border: playerScoresForTask[c.id] === score ? "2px solid var(--tm-red)" : "1px solid var(--tm-cream-dark)",
210-
background: playerScoresForTask[c.id] === score ? "var(--tm-red-bright)" : "white",
211-
color: playerScoresForTask[c.id] === score ? "white" : "var(--tm-text-dark)",
212-
fontWeight: 700,
213-
cursor: "pointer",
214-
fontSize: "1rem",
215-
}}
216-
>
217-
{score}
218-
</button>
219-
))}
220-
</div>
248+
<p style={{ margin: 0, fontSize: "0.85rem", color: "#94a3b8" }}>
249+
<span style={{ marginRight: "0.4rem" }}>💡</span>
250+
{currentFact}
251+
</p>
221252
</div>
222-
))}
253+
)}
254+
</div>
255+
);
256+
}
257+
258+
// Subjective/combo task view
259+
return (
260+
<div>
261+
<div className="card">
262+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "1rem" }}>
263+
<h2>
264+
Task {currentTaskIdx + 1}/{tasks.length}
265+
</h2>
266+
<button onClick={onCancel} style={{ background: "none", border: "1px solid var(--tm-cream-dark)", color: "var(--tm-text-muted)", padding: "0.4rem 1rem", borderRadius: "6px", cursor: "pointer" }}>
267+
Cancel
268+
</button>
269+
</div>
270+
271+
<div style={{ background: "var(--tm-cream-dark)", padding: "1rem", borderRadius: "8px", marginBottom: "1rem" }}>
272+
<p style={{ color: "var(--tm-red)", fontWeight: 700, marginBottom: "0.5rem" }}>{currentTask.name}</p>
273+
<p style={{ color: "var(--tm-text-muted)", fontSize: "0.85rem" }}>
274+
Type: {currentTask.judgement} · Step {currentStep}/{totalSteps}
275+
</p>
276+
</div>
277+
278+
<div style={{
279+
background: `${currentPlayer.color}20`,
280+
border: `2px solid ${currentPlayer.color}`,
281+
borderRadius: "8px",
282+
padding: "1rem",
283+
marginBottom: "1rem",
284+
textAlign: "center",
285+
}}>
286+
<p style={{ fontSize: "1.2rem", fontWeight: 700 }}>
287+
🎯 {currentPlayer.name}, score this task!
288+
</p>
289+
<p style={{ color: "var(--tm-text-muted)", fontSize: "0.85rem" }}>
290+
Award 1-5 points to each contestant
291+
</p>
292+
</div>
293+
294+
<div style={{ display: "flex", flexDirection: "column", gap: "0.75rem" }}>
295+
{currentTask.contestants.map((c) => (
296+
<div key={c.id} style={{
297+
display: "flex",
298+
alignItems: "center",
299+
justifyContent: "space-between",
300+
background: "var(--tm-cream-dark)",
301+
}}>
302+
<span style={{ fontWeight: 600 }}>{c.name}</span>
303+
<div style={{ display: "flex", gap: "0.25rem" }}>
304+
{[1, 2, 3, 4, 5].map((score) => (
305+
<button
306+
key={score}
307+
onClick={() => setScore(c.id, score)}
308+
style={{
309+
width: "40px",
310+
height: "40px",
311+
borderRadius: "8px",
312+
border: playerScoresForTask[c.id] === score ? "2px solid var(--tm-red)" : "1px solid var(--tm-cream-dark)",
313+
background: playerScoresForTask[c.id] === score ? "var(--tm-red-bright)" : "white",
314+
color: playerScoresForTask[c.id] === score ? "white" : "var(--tm-text-dark)",
315+
fontWeight: 700,
316+
cursor: "pointer",
317+
fontSize: "1rem",
318+
}}
319+
>
320+
{score}
321+
</button>
322+
))}
323+
</div>
324+
</div>
325+
))}
326+
</div>
327+
328+
<button
329+
className="btn-primary"
330+
style={{ width: "100%", marginTop: "1rem", padding: "0.75rem" }}
331+
onClick={handleNext}
332+
disabled={!allContestantsScored}
333+
>
334+
{currentTaskIdx === tasks.length - 1 && currentPlayerIdx === players.length - 1
335+
? "🏆 See Results!"
336+
: currentPlayerIdx === players.length - 1
337+
? "Next Task →"
338+
: `Next: ${players[currentPlayerIdx + 1].name}'s turn →`}
339+
</button>
223340
</div>
224341

225342
{currentFact && (
@@ -236,19 +353,6 @@ export default function WatchModeScorer({ tasks, players, seasonNumber, onComple
236353
</p>
237354
</div>
238355
)}
239-
240-
<button
241-
className="btn-primary"
242-
style={{ width: "100%", marginTop: "1rem", padding: "0.75rem" }}
243-
onClick={handleNext}
244-
disabled={!allContestantsScored}
245-
>
246-
{currentTaskIdx === scorableTasks.length - 1 && currentPlayerIdx === players.length - 1
247-
? "🏆 See Results!"
248-
: currentPlayerIdx === players.length - 1
249-
? "Next Task →"
250-
: `Next: ${players[currentPlayerIdx + 1].name}'s turn →`}
251-
</button>
252356
</div>
253357
);
254358
}

0 commit comments

Comments
 (0)