Felix Zieger commited on
Commit
aeb9637
·
1 Parent(s): 818d1f7
README.md CHANGED
@@ -17,8 +17,6 @@ However, you can only say one word at a time, taking turns with another AI.
17
 
18
  You need Node.js and npm installed on your system.
19
 
20
- Add a .env file with `VITE_MISTRAL_API_KEY=xxx` to query Mistral LLM from your local environment.
21
-
22
  ```
23
  npm i
24
  npm run dev
 
17
 
18
  You need Node.js and npm installed on your system.
19
 
 
 
20
  ```
21
  npm i
22
  npm run dev
src/components/GameContainer.tsx CHANGED
@@ -1,5 +1,7 @@
1
  import { useState, KeyboardEvent, useEffect, useContext } from "react";
2
- import { getRandomWord } from "@/lib/words";
 
 
3
  import { motion } from "framer-motion";
4
  import { generateAIResponse, guessWord } from "@/services/mistralService";
5
  import { getThemedWord } from "@/services/themeService";
@@ -10,6 +12,7 @@ import { SentenceBuilder } from "./game/SentenceBuilder";
10
  import { GuessDisplay } from "./game/GuessDisplay";
11
  import { useTranslation } from "@/hooks/useTranslation";
12
  import { LanguageContext } from "@/contexts/LanguageContext";
 
13
 
14
  type GameState = "welcome" | "theme-selection" | "building-sentence" | "showing-guess";
15
 
@@ -25,6 +28,7 @@ export const GameContainer = () => {
25
  const [totalWords, setTotalWords] = useState<number>(0);
26
  const [usedWords, setUsedWords] = useState<string[]>([]);
27
  const [sessionId, setSessionId] = useState<string>("");
 
28
  const { toast } = useToast();
29
  const t = useTranslation();
30
  const { language } = useContext(LanguageContext);
@@ -37,13 +41,14 @@ export const GameContainer = () => {
37
 
38
  useEffect(() => {
39
  const handleKeyPress = (e: KeyboardEvent) => {
40
- if (e.key === 'Enter') {
41
  if (gameState === 'welcome') {
42
  handleStart();
43
  } else if (gameState === 'showing-guess') {
44
- const correct = isGuessCorrect();
45
- if (correct) {
46
  handleNextRound();
 
 
47
  }
48
  }
49
  }
@@ -51,7 +56,7 @@ export const GameContainer = () => {
51
 
52
  window.addEventListener('keydown', handleKeyPress as any);
53
  return () => window.removeEventListener('keydown', handleKeyPress as any);
54
- }, [gameState, aiGuess, currentWord]);
55
 
56
  const handleStart = () => {
57
  setGameState("theme-selection");
@@ -72,9 +77,20 @@ export const GameContainer = () => {
72
  const handleThemeSelect = async (theme: string) => {
73
  setCurrentTheme(theme);
74
  try {
75
- const word = theme === "standard" ?
76
- getRandomWord(language) :
77
- await getThemedWord(theme, usedWords, language);
 
 
 
 
 
 
 
 
 
 
 
78
  setCurrentWord(word);
79
  setGameState("building-sentence");
80
  setSuccessfulRounds(0);
@@ -119,6 +135,28 @@ export const GameContainer = () => {
119
  }
120
  };
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  const handleMakeGuess = async () => {
123
  setIsAiThinking(true);
124
  try {
@@ -135,6 +173,11 @@ export const GameContainer = () => {
135
  const sentenceString = finalSentence.join(' ');
136
  const guess = await guessWord(sentenceString, language);
137
  setAiGuess(guess);
 
 
 
 
 
138
  setGameState("showing-guess");
139
  } catch (error) {
140
  console.error('Error getting AI guess:', error);
@@ -152,9 +195,20 @@ export const GameContainer = () => {
152
  if (handleGuessComplete()) {
153
  const getNewWord = async () => {
154
  try {
155
- const word = currentTheme === "standard" ?
156
- getRandomWord(language) :
157
- await getThemedWord(currentTheme, usedWords, language);
 
 
 
 
 
 
 
 
 
 
 
158
  setCurrentWord(word);
159
  setGameState("building-sentence");
160
  setSentence([]);
@@ -232,13 +286,15 @@ export const GameContainer = () => {
232
  currentWord={currentWord}
233
  onNextRound={handleNextRound}
234
  onPlayAgain={handlePlayAgain}
 
235
  currentScore={successfulRounds}
236
  avgWordsPerRound={getAverageWordsPerRound()}
237
  sessionId={sessionId}
238
- onBack={handleBack}
 
239
  />
240
  )}
241
  </motion.div>
242
  </div>
243
  );
244
- };
 
1
  import { useState, KeyboardEvent, useEffect, useContext } from "react";
2
+ import { getRandomWord } from "@/lib/words-standard";
3
+ import { getRandomSportsWord } from "@/lib/words-sports";
4
+ import { getRandomFoodWord } from "@/lib/words-food";
5
  import { motion } from "framer-motion";
6
  import { generateAIResponse, guessWord } from "@/services/mistralService";
7
  import { getThemedWord } from "@/services/themeService";
 
12
  import { GuessDisplay } from "./game/GuessDisplay";
13
  import { useTranslation } from "@/hooks/useTranslation";
14
  import { LanguageContext } from "@/contexts/LanguageContext";
15
+ import { supabase } from "@/integrations/supabase/client";
16
 
17
  type GameState = "welcome" | "theme-selection" | "building-sentence" | "showing-guess";
18
 
 
28
  const [totalWords, setTotalWords] = useState<number>(0);
29
  const [usedWords, setUsedWords] = useState<string[]>([]);
30
  const [sessionId, setSessionId] = useState<string>("");
31
+ const [isHighScoreDialogOpen, setIsHighScoreDialogOpen] = useState(false);
32
  const { toast } = useToast();
33
  const t = useTranslation();
34
  const { language } = useContext(LanguageContext);
 
41
 
42
  useEffect(() => {
43
  const handleKeyPress = (e: KeyboardEvent) => {
44
+ if (e.key === 'Enter' && !isHighScoreDialogOpen) {
45
  if (gameState === 'welcome') {
46
  handleStart();
47
  } else if (gameState === 'showing-guess') {
48
+ if (isGuessCorrect()) {
 
49
  handleNextRound();
50
+ } else {
51
+ handlePlayAgain();
52
  }
53
  }
54
  }
 
56
 
57
  window.addEventListener('keydown', handleKeyPress as any);
58
  return () => window.removeEventListener('keydown', handleKeyPress as any);
59
+ }, [gameState, aiGuess, currentWord, isHighScoreDialogOpen]);
60
 
61
  const handleStart = () => {
62
  setGameState("theme-selection");
 
77
  const handleThemeSelect = async (theme: string) => {
78
  setCurrentTheme(theme);
79
  try {
80
+ let word;
81
+ switch (theme) {
82
+ case "sports":
83
+ word = getRandomSportsWord(language);
84
+ break;
85
+ case "food":
86
+ word = getRandomFoodWord(language);
87
+ break;
88
+ case "standard":
89
+ word = getRandomWord(language);
90
+ break;
91
+ default:
92
+ word = await getThemedWord(theme, usedWords, language);
93
+ }
94
  setCurrentWord(word);
95
  setGameState("building-sentence");
96
  setSuccessfulRounds(0);
 
135
  }
136
  };
137
 
138
+ const saveGameResult = async (sentence: string[], aiGuess: string, isCorrect: boolean) => {
139
+ try {
140
+ const { error } = await supabase
141
+ .from('game_results')
142
+ .insert({
143
+ target_word: currentWord,
144
+ description: sentence.join(' '),
145
+ ai_guess: aiGuess,
146
+ is_correct: isCorrect,
147
+ session_id: sessionId
148
+ });
149
+
150
+ if (error) {
151
+ console.error('Error saving game result:', error);
152
+ } else {
153
+ console.log('Game result saved successfully');
154
+ }
155
+ } catch (error) {
156
+ console.error('Error saving game result:', error);
157
+ }
158
+ };
159
+
160
  const handleMakeGuess = async () => {
161
  setIsAiThinking(true);
162
  try {
 
173
  const sentenceString = finalSentence.join(' ');
174
  const guess = await guessWord(sentenceString, language);
175
  setAiGuess(guess);
176
+
177
+ // Save game result in the background
178
+ saveGameResult(finalSentence, guess, guess.toLowerCase() === currentWord.toLowerCase())
179
+ .catch(error => console.error('Background save failed:', error));
180
+
181
  setGameState("showing-guess");
182
  } catch (error) {
183
  console.error('Error getting AI guess:', error);
 
195
  if (handleGuessComplete()) {
196
  const getNewWord = async () => {
197
  try {
198
+ let word;
199
+ switch (currentTheme) {
200
+ case "sports":
201
+ word = getRandomSportsWord(language);
202
+ break;
203
+ case "food":
204
+ word = getRandomFoodWord(language);
205
+ break;
206
+ case "standard":
207
+ word = getRandomWord(language);
208
+ break;
209
+ default:
210
+ word = await getThemedWord(currentTheme, usedWords, language);
211
+ }
212
  setCurrentWord(word);
213
  setGameState("building-sentence");
214
  setSentence([]);
 
286
  currentWord={currentWord}
287
  onNextRound={handleNextRound}
288
  onPlayAgain={handlePlayAgain}
289
+ onBack={handleBack}
290
  currentScore={successfulRounds}
291
  avgWordsPerRound={getAverageWordsPerRound()}
292
  sessionId={sessionId}
293
+ currentTheme={currentTheme}
294
+ onHighScoreDialogChange={setIsHighScoreDialogOpen}
295
  />
296
  )}
297
  </motion.div>
298
  </div>
299
  );
300
+ };
src/components/HighScoreBoard.tsx CHANGED
@@ -1,26 +1,13 @@
1
- import { useEffect, useState } from "react";
2
- import { Button } from "@/components/ui/button";
3
- import { Input } from "@/components/ui/input";
4
  import { supabase } from "@/integrations/supabase/client";
5
  import { useQuery, useQueryClient } from "@tanstack/react-query";
6
- import {
7
- Table,
8
- TableBody,
9
- TableCell,
10
- TableHead,
11
- TableHeader,
12
- TableRow,
13
- } from "@/components/ui/table";
14
  import { useToast } from "@/components/ui/use-toast";
15
- import {
16
- Pagination,
17
- PaginationContent,
18
- PaginationItem,
19
- PaginationLink,
20
- PaginationNext,
21
- PaginationPrevious,
22
- } from "@/components/ui/pagination";
23
  import { useTranslation } from "@/hooks/useTranslation";
 
 
 
 
 
24
 
25
  interface HighScore {
26
  id: string;
@@ -29,55 +16,52 @@ interface HighScore {
29
  avg_words_per_round: number;
30
  created_at: string;
31
  session_id: string;
 
32
  }
33
 
34
  interface HighScoreBoardProps {
35
- currentScore: number;
36
- avgWordsPerRound: number;
37
- onClose: () => void;
38
- onPlayAgain: () => void;
39
- sessionId: string;
40
  onScoreSubmitted?: () => void;
 
 
41
  }
42
 
43
  const ITEMS_PER_PAGE = 5;
44
- const MAX_PAGES = 5;
45
-
46
- const getRankMedal = (rank: number) => {
47
- switch (rank) {
48
- case 1:
49
- return "🥇";
50
- case 2:
51
- return "🥈";
52
- case 3:
53
- return "🥉";
54
- default:
55
- return null;
56
- }
57
- };
58
 
59
  export const HighScoreBoard = ({
60
- currentScore,
61
- avgWordsPerRound,
62
  onClose,
63
- sessionId,
64
  onScoreSubmitted,
 
 
65
  }: HighScoreBoardProps) => {
66
  const [playerName, setPlayerName] = useState("");
67
  const [isSubmitting, setIsSubmitting] = useState(false);
68
  const [hasSubmitted, setHasSubmitted] = useState(false);
69
  const [currentPage, setCurrentPage] = useState(1);
 
 
 
70
  const { toast } = useToast();
71
  const t = useTranslation();
72
  const queryClient = useQueryClient();
73
 
74
- const { data: highScores, refetch } = useQuery({
75
- queryKey: ["highScores"],
 
 
76
  queryFn: async () => {
77
- console.log("Fetching high scores...");
78
  const { data, error } = await supabase
79
  .from("high_scores")
80
  .select("*")
 
81
  .order("score", { ascending: false })
82
  .order("avg_words_per_round", { ascending: true });
83
 
@@ -120,78 +104,35 @@ export const HighScoreBoard = ({
120
 
121
  setIsSubmitting(true);
122
  try {
123
- console.log("Checking existing score for player:", playerName.trim());
124
- const { data: existingScores, error: fetchError } = await supabase
125
- .from("high_scores")
126
- .select("*")
127
- .eq("player_name", playerName.trim());
 
 
 
 
 
128
 
129
- if (fetchError) {
130
- console.error("Error fetching existing scores:", fetchError);
131
- throw fetchError;
132
  }
133
 
134
- const existingScore = existingScores?.[0];
135
- console.log("Existing score found:", existingScore);
136
-
137
- if (existingScore) {
138
- if (currentScore > existingScore.score) {
139
- console.log("Updating existing score...");
140
- const { error: updateError } = await supabase
141
- .from("high_scores")
142
- .update({
143
- score: currentScore,
144
- avg_words_per_round: avgWordsPerRound,
145
- session_id: sessionId
146
- })
147
- .eq("id", existingScore.id);
148
-
149
- if (updateError) {
150
- console.error("Error updating score:", updateError);
151
- throw updateError;
152
- }
153
-
154
- toast({
155
- title: t.leaderboard.error.newHighScore,
156
- description: t.leaderboard.error.beatRecord.replace(
157
- "{score}",
158
- String(existingScore.score)
159
- ),
160
- });
161
-
162
- console.log("Score updated successfully");
163
- await queryClient.invalidateQueries({ queryKey: ["highScores"] });
164
- } else {
165
- toast({
166
- title: t.leaderboard.error.notHigher
167
- .replace("{current}", String(currentScore))
168
- .replace("{best}", String(existingScore.score)),
169
- variant: "destructive",
170
- });
171
- setIsSubmitting(false);
172
- return;
173
- }
174
- } else {
175
- console.log("Inserting new score...");
176
- const { error: insertError } = await supabase.from("high_scores").insert({
177
- player_name: playerName.trim(),
178
- score: currentScore,
179
- avg_words_per_round: avgWordsPerRound,
180
- session_id: sessionId
181
  });
182
-
183
- if (insertError) {
184
- console.error("Error inserting score:", insertError);
185
- throw insertError;
186
- }
187
 
188
- console.log("New score inserted successfully");
 
 
189
  await queryClient.invalidateQueries({ queryKey: ["highScores"] });
190
  }
191
-
192
- setHasSubmitted(true);
193
- onScoreSubmitted?.();
194
- setPlayerName("");
195
  } catch (error) {
196
  console.error("Error submitting score:", error);
197
  toast({
@@ -211,141 +152,47 @@ export const HighScoreBoard = ({
211
  }
212
  };
213
 
214
- const totalPages = highScores ? Math.min(Math.ceil(highScores.length / ITEMS_PER_PAGE), MAX_PAGES) : 0;
215
  const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
216
  const paginatedScores = highScores?.slice(startIndex, startIndex + ITEMS_PER_PAGE);
217
 
218
- const handlePreviousPage = () => {
219
- if (currentPage > 1) {
220
- setCurrentPage(p => p - 1);
221
- }
222
- };
223
-
224
- const handleNextPage = () => {
225
- if (currentPage < totalPages) {
226
- setCurrentPage(p => p + 1);
227
- }
228
- };
229
-
230
- useEffect(() => {
231
- const handleKeyDown = (e: KeyboardEvent) => {
232
- if (e.key === 'ArrowLeft') {
233
- handlePreviousPage();
234
- } else if (e.key === 'ArrowRight') {
235
- handleNextPage();
236
- }
237
- };
238
-
239
- window.addEventListener('keydown', handleKeyDown);
240
- return () => window.removeEventListener('keydown', handleKeyDown);
241
- }, [currentPage, totalPages]);
242
-
243
  return (
244
  <div className="space-y-6">
245
- <div className="text-center">
246
- <h2 className="text-2xl font-bold mb-2">{t.leaderboard.title}</h2>
247
- <p className="text-gray-600">
248
- {t.leaderboard.yourScore}: {currentScore} {t.leaderboard.roundCount}
249
- {currentScore > 0 &&
250
- ` (${avgWordsPerRound.toFixed(1)} ${t.leaderboard.wordsPerRound})`}
251
- </p>
252
- </div>
 
 
 
 
253
 
254
  {!hasSubmitted && currentScore > 0 && (
255
- <div className="flex gap-4 mb-6">
256
- <Input
257
- placeholder={t.leaderboard.enterName}
258
- value={playerName}
259
- onChange={(e) => {
260
- const value = e.target.value.replace(/[^a-zA-ZÀ-ÿ0-9]/g, "");
261
- setPlayerName(value);
262
- }}
263
- onKeyDown={handleKeyDown}
264
- className="flex-1"
265
- maxLength={20}
266
- />
267
- <Button
268
- onClick={handleSubmitScore}
269
- disabled={isSubmitting || !playerName.trim() || hasSubmitted}
270
- >
271
- {isSubmitting ? t.leaderboard.submitting : t.leaderboard.submit}
272
- </Button>
273
- </div>
274
  )}
275
 
276
- <div className="rounded-md border">
277
- <Table>
278
- <TableHeader>
279
- <TableRow>
280
- <TableHead>{t.leaderboard.rank}</TableHead>
281
- <TableHead>{t.leaderboard.player}</TableHead>
282
- <TableHead>{t.leaderboard.roundsColumn}</TableHead>
283
- <TableHead>{t.leaderboard.avgWords}</TableHead>
284
- </TableRow>
285
- </TableHeader>
286
- <TableBody>
287
- {paginatedScores?.map((score, index) => {
288
- const absoluteRank = startIndex + index + 1;
289
- const medal = getRankMedal(absoluteRank);
290
- return (
291
- <TableRow key={score.id}>
292
- <TableCell>
293
- {absoluteRank}
294
- {medal && <span className="ml-2">{medal}</span>}
295
- </TableCell>
296
- <TableCell>{score.player_name}</TableCell>
297
- <TableCell>{score.score}</TableCell>
298
- <TableCell>{score.avg_words_per_round.toFixed(1)}</TableCell>
299
- </TableRow>
300
- );
301
- })}
302
- {!paginatedScores?.length && (
303
- <TableRow>
304
- <TableCell colSpan={4} className="text-center">
305
- {t.leaderboard.noScores}
306
- </TableCell>
307
- </TableRow>
308
- )}
309
- </TableBody>
310
- </Table>
311
- </div>
312
-
313
- {totalPages > 1 && (
314
- <Pagination>
315
- <PaginationContent>
316
- <PaginationItem>
317
- <PaginationPrevious
318
- onClick={handlePreviousPage}
319
- className={currentPage === 1 ? "pointer-events-none opacity-50" : ""}
320
- >
321
- <span className="hidden sm:inline">{t.leaderboard.previous}</span>
322
- <span className="text-xs text-muted-foreground ml-1">←</span>
323
- </PaginationPrevious>
324
- </PaginationItem>
325
- {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
326
- <PaginationItem key={page}>
327
- <PaginationLink
328
- onClick={() => setCurrentPage(page)}
329
- isActive={currentPage === page}
330
- >
331
- {page}
332
- </PaginationLink>
333
- </PaginationItem>
334
- ))}
335
- <PaginationItem>
336
- <PaginationNext
337
- onClick={handleNextPage}
338
- className={
339
- currentPage === totalPages ? "pointer-events-none opacity-50" : ""
340
- }
341
- >
342
- <span className="hidden sm:inline">{t.leaderboard.next}</span>
343
- <span className="text-xs text-muted-foreground ml-1">→</span>
344
- </PaginationNext>
345
- </PaginationItem>
346
- </PaginationContent>
347
- </Pagination>
348
- )}
349
  </div>
350
  );
351
- };
 
1
+ import { useState } from "react";
 
 
2
  import { supabase } from "@/integrations/supabase/client";
3
  import { useQuery, useQueryClient } from "@tanstack/react-query";
 
 
 
 
 
 
 
 
4
  import { useToast } from "@/components/ui/use-toast";
 
 
 
 
 
 
 
 
5
  import { useTranslation } from "@/hooks/useTranslation";
6
+ import { ThemeFilter } from "./game/leaderboard/ThemeFilter";
7
+ import { ScoreSubmissionForm } from "./game/leaderboard/ScoreSubmissionForm";
8
+ import { ScoresTable } from "./game/leaderboard/ScoresTable";
9
+ import { LeaderboardHeader } from "./game/leaderboard/LeaderboardHeader";
10
+ import { LeaderboardPagination } from "./game/leaderboard/LeaderboardPagination";
11
 
12
  interface HighScore {
13
  id: string;
 
16
  avg_words_per_round: number;
17
  created_at: string;
18
  session_id: string;
19
+ theme: string;
20
  }
21
 
22
  interface HighScoreBoardProps {
23
+ currentScore?: number;
24
+ avgWordsPerRound?: number;
25
+ onClose?: () => void;
26
+ onPlayAgain?: () => void;
27
+ sessionId?: string;
28
  onScoreSubmitted?: () => void;
29
+ showThemeFilter?: boolean;
30
+ initialTheme?: string;
31
  }
32
 
33
  const ITEMS_PER_PAGE = 5;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  export const HighScoreBoard = ({
36
+ currentScore = 0,
37
+ avgWordsPerRound = 0,
38
  onClose,
39
+ sessionId = "",
40
  onScoreSubmitted,
41
+ showThemeFilter = true,
42
+ initialTheme = "standard",
43
  }: HighScoreBoardProps) => {
44
  const [playerName, setPlayerName] = useState("");
45
  const [isSubmitting, setIsSubmitting] = useState(false);
46
  const [hasSubmitted, setHasSubmitted] = useState(false);
47
  const [currentPage, setCurrentPage] = useState(1);
48
+ const [selectedTheme, setSelectedTheme] = useState<'standard' | 'sports' | 'food' | 'custom'>(
49
+ initialTheme as 'standard' | 'sports' | 'food' | 'custom'
50
+ );
51
  const { toast } = useToast();
52
  const t = useTranslation();
53
  const queryClient = useQueryClient();
54
 
55
+ const showScoreInfo = sessionId !== "" && currentScore > 0;
56
+
57
+ const { data: highScores } = useQuery({
58
+ queryKey: ["highScores", selectedTheme],
59
  queryFn: async () => {
60
+ console.log("Fetching high scores for theme:", selectedTheme);
61
  const { data, error } = await supabase
62
  .from("high_scores")
63
  .select("*")
64
+ .eq('theme', selectedTheme)
65
  .order("score", { ascending: false })
66
  .order("avg_words_per_round", { ascending: true });
67
 
 
104
 
105
  setIsSubmitting(true);
106
  try {
107
+ console.log("Submitting score via Edge Function...");
108
+ const { data, error } = await supabase.functions.invoke('submit-high-score', {
109
+ body: {
110
+ playerName: playerName.trim(),
111
+ score: currentScore,
112
+ avgWordsPerRound,
113
+ sessionId,
114
+ theme: selectedTheme
115
+ }
116
+ });
117
 
118
+ if (error) {
119
+ console.error("Error submitting score:", error);
120
+ throw error;
121
  }
122
 
123
+ console.log("Score submitted successfully:", data);
124
+
125
+ if (data.success) {
126
+ toast({
127
+ title: t.leaderboard.success,
128
+ description: t.leaderboard.success,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  });
 
 
 
 
 
130
 
131
+ setHasSubmitted(true);
132
+ onScoreSubmitted?.();
133
+ setPlayerName("");
134
  await queryClient.invalidateQueries({ queryKey: ["highScores"] });
135
  }
 
 
 
 
136
  } catch (error) {
137
  console.error("Error submitting score:", error);
138
  toast({
 
152
  }
153
  };
154
 
155
+ const totalPages = highScores ? Math.ceil(highScores.length / ITEMS_PER_PAGE) : 0;
156
  const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
157
  const paginatedScores = highScores?.slice(startIndex, startIndex + ITEMS_PER_PAGE);
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  return (
160
  <div className="space-y-6">
161
+ <LeaderboardHeader
162
+ currentScore={currentScore}
163
+ avgWordsPerRound={avgWordsPerRound}
164
+ showScoreInfo={showScoreInfo}
165
+ />
166
+
167
+ {showThemeFilter && (
168
+ <ThemeFilter
169
+ selectedTheme={selectedTheme}
170
+ onThemeChange={setSelectedTheme}
171
+ />
172
+ )}
173
 
174
  {!hasSubmitted && currentScore > 0 && (
175
+ <ScoreSubmissionForm
176
+ playerName={playerName}
177
+ setPlayerName={setPlayerName}
178
+ isSubmitting={isSubmitting}
179
+ hasSubmitted={hasSubmitted}
180
+ onSubmit={handleSubmitScore}
181
+ onKeyDown={handleKeyDown}
182
+ />
 
 
 
 
 
 
 
 
 
 
 
183
  )}
184
 
185
+ <ScoresTable
186
+ scores={paginatedScores || []}
187
+ startIndex={startIndex}
188
+ />
189
+
190
+ <LeaderboardPagination
191
+ currentPage={currentPage}
192
+ totalPages={totalPages}
193
+ onPreviousPage={() => setCurrentPage(p => Math.max(1, p - 1))}
194
+ onNextPage={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
195
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  </div>
197
  );
198
+ };
src/components/game/GuessDisplay.tsx CHANGED
@@ -24,6 +24,8 @@ interface GuessDisplayProps {
24
  currentScore: number;
25
  avgWordsPerRound: number;
26
  sessionId: string;
 
 
27
  }
28
 
29
  export const GuessDisplay = ({
@@ -36,26 +38,25 @@ export const GuessDisplay = ({
36
  currentScore,
37
  avgWordsPerRound,
38
  sessionId,
 
 
39
  }: GuessDisplayProps) => {
40
  const [showConfirmDialog, setShowConfirmDialog] = useState(false);
41
  const [hasSubmittedScore, setHasSubmittedScore] = useState(false);
 
42
  const t = useTranslation();
43
 
44
- console.log("GuessDisplay - Rendering with showConfirmDialog:", showConfirmDialog);
 
 
45
 
46
  const handleSetShowConfirmDialog = (show: boolean) => {
47
- console.log("GuessDisplay - Setting showConfirmDialog to:", show);
48
  setShowConfirmDialog(show);
49
  };
50
 
51
- useEffect(() => {
52
- console.log("GuessDisplay - showConfirmDialog changed to:", showConfirmDialog);
53
- }, [showConfirmDialog]);
54
-
55
  const isGuessCorrect = () => aiGuess.toLowerCase() === currentWord.toLowerCase();
56
 
57
  const handleScoreSubmitted = () => {
58
- console.log('Score submitted, updating state');
59
  setHasSubmittedScore(true);
60
  };
61
 
@@ -85,8 +86,25 @@ export const GuessDisplay = ({
85
  currentScore={currentScore}
86
  avgWordsPerRound={avgWordsPerRound}
87
  sessionId={sessionId}
 
88
  onScoreSubmitted={handleScoreSubmitted}
 
89
  />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  </motion.div>
91
  );
92
  };
 
24
  currentScore: number;
25
  avgWordsPerRound: number;
26
  sessionId: string;
27
+ currentTheme: string;
28
+ onHighScoreDialogChange?: (isOpen: boolean) => void;
29
  }
30
 
31
  export const GuessDisplay = ({
 
38
  currentScore,
39
  avgWordsPerRound,
40
  sessionId,
41
+ currentTheme,
42
+ onHighScoreDialogChange,
43
  }: GuessDisplayProps) => {
44
  const [showConfirmDialog, setShowConfirmDialog] = useState(false);
45
  const [hasSubmittedScore, setHasSubmittedScore] = useState(false);
46
+ const [showHighScores, setShowHighScores] = useState(false);
47
  const t = useTranslation();
48
 
49
+ useEffect(() => {
50
+ onHighScoreDialogChange?.(showHighScores);
51
+ }, [showHighScores, onHighScoreDialogChange]);
52
 
53
  const handleSetShowConfirmDialog = (show: boolean) => {
 
54
  setShowConfirmDialog(show);
55
  };
56
 
 
 
 
 
57
  const isGuessCorrect = () => aiGuess.toLowerCase() === currentWord.toLowerCase();
58
 
59
  const handleScoreSubmitted = () => {
 
60
  setHasSubmittedScore(true);
61
  };
62
 
 
86
  currentScore={currentScore}
87
  avgWordsPerRound={avgWordsPerRound}
88
  sessionId={sessionId}
89
+ currentTheme={currentTheme}
90
  onScoreSubmitted={handleScoreSubmitted}
91
+ onHighScoreDialogChange={setShowHighScores}
92
  />
93
+
94
+ <Dialog open={showHighScores} onOpenChange={setShowHighScores}>
95
+ <DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-[600px]">
96
+ <HighScoreBoard
97
+ currentScore={currentScore}
98
+ avgWordsPerRound={avgWordsPerRound}
99
+ onClose={() => setShowHighScores(false)}
100
+ onPlayAgain={onPlayAgain}
101
+ sessionId={sessionId}
102
+ showThemeFilter={false}
103
+ initialTheme={currentTheme}
104
+ onScoreSubmitted={handleScoreSubmitted}
105
+ />
106
+ </DialogContent>
107
+ </Dialog>
108
  </motion.div>
109
  );
110
  };
src/components/game/WelcomeScreen.tsx CHANGED
@@ -1,13 +1,13 @@
1
- import { Button } from "@/components/ui/button";
2
  import { motion } from "framer-motion";
3
  import { useState } from "react";
4
  import { HighScoreBoard } from "../HighScoreBoard";
5
- import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
6
  import { LanguageSelector } from "./LanguageSelector";
7
  import { useTranslation } from "@/hooks/useTranslation";
8
- import { useContext } from "react";
9
- import { LanguageContext } from "@/contexts/LanguageContext";
10
- import { Heart } from "lucide-react";
 
11
 
12
  interface WelcomeScreenProps {
13
  onStart: () => void;
@@ -17,7 +17,6 @@ export const WelcomeScreen = ({ onStart }: WelcomeScreenProps) => {
17
  const [showHighScores, setShowHighScores] = useState(false);
18
  const [showHowToPlay, setShowHowToPlay] = useState(false);
19
  const t = useTranslation();
20
- const { language } = useContext(LanguageContext);
21
 
22
  return (
23
  <>
@@ -35,103 +34,45 @@ export const WelcomeScreen = ({ onStart }: WelcomeScreenProps) => {
35
  </p>
36
  </div>
37
 
38
- <div className="space-y-4">
39
- <Button
40
- onClick={onStart}
41
- className="w-full bg-primary text-lg hover:bg-primary/90"
42
- >
43
- {t.welcome.startButton} ⏎
44
- </Button>
45
- <div className="grid grid-cols-2 gap-4">
46
- <Button
47
- onClick={() => setShowHowToPlay(true)}
48
- variant="outline"
49
- className="text-lg"
50
- >
51
- {t.welcome.howToPlay} 📖
52
- </Button>
53
- <Button
54
- onClick={() => setShowHighScores(true)}
55
- variant="outline"
56
- className="text-lg"
57
- >
58
- {t.welcome.leaderboard} 🏆
59
- </Button>
60
- </div>
61
- </div>
62
  </motion.div>
63
 
64
  <motion.div
65
  initial={{ opacity: 0 }}
66
  animate={{ opacity: 1 }}
67
  transition={{ delay: 0.2 }}
68
- className="max-w-2xl mx-auto text-center mt-12 space-y-4"
69
  >
70
- <p className="text-sm text-gray-500">
71
- {t.welcome.credits}{" "}
72
- <a
73
- href="https://blog.felixzieger.de/gaming-hackathon-paris/"
74
- target="_blank"
75
- rel="noopener noreferrer"
76
- className="text-primary hover:underline"
77
- >
78
- AI Gaming Hackathon
79
- </a>.
80
- </p>
81
- <div className="flex flex-col items-center gap-2">
82
- <p className="text-sm text-gray-600">{t.welcome.helpWin}</p>
83
- <a
84
- href="https://huggingface.co/spaces/Mistral-AI-Game-Jam/description-improv/tree/main"
85
- target="_blank"
86
- rel="noopener noreferrer"
87
- className="inline-flex flex-col items-center gap-2 px-4 py-2 text-sm font-bold text-primary hover:text-primary/90 transition-colors border border-primary/20 rounded-md hover:border-primary/40"
88
- >
89
- <span className="inline-flex items-center gap-2">
90
- <Heart className="w-4 h-4" /> {t.welcome.onHuggingface}
91
- </span>
92
- </a>
93
- </div>
94
  </motion.div>
95
 
96
  <Dialog open={showHighScores} onOpenChange={setShowHighScores}>
97
  <DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-[600px]">
98
  <HighScoreBoard
99
- currentScore={0}
100
- avgWordsPerRound={0}
101
  onClose={() => setShowHighScores(false)}
102
  onPlayAgain={onStart}
103
- sessionId=""
104
  />
105
  </DialogContent>
106
  </Dialog>
107
 
108
- <Dialog open={showHowToPlay} onOpenChange={setShowHowToPlay}>
109
- <DialogContent className="sm:max-w-[600px]">
110
- <DialogHeader>
111
- <DialogTitle className="text-xl font-semibold text-primary">{t.welcome.howToPlay}</DialogTitle>
112
- </DialogHeader>
113
- <div className="space-y-6">
114
- <div className="grid gap-4 text-gray-600">
115
- <div>
116
- <h3 className="font-medium text-gray-800">{t.howToPlay.setup.title}</h3>
117
- <p>{t.howToPlay.setup.description}</p>
118
- </div>
119
- <div>
120
- <h3 className="font-medium text-gray-800">{t.howToPlay.goal.title}</h3>
121
- <p>{t.howToPlay.goal.description}</p>
122
- </div>
123
- <div>
124
- <h3 className="font-medium text-gray-800">{t.howToPlay.rules.title}</h3>
125
- <ul className="list-disc list-inside space-y-1">
126
- {t.howToPlay.rules.items.map((rule, index) => (
127
- <li key={index}>{rule}</li>
128
- ))}
129
- </ul>
130
- </div>
131
- </div>
132
- </div>
133
- </DialogContent>
134
- </Dialog>
135
  </>
136
  );
137
- };
 
 
1
  import { motion } from "framer-motion";
2
  import { useState } from "react";
3
  import { HighScoreBoard } from "../HighScoreBoard";
4
+ import { Dialog, DialogContent } from "@/components/ui/dialog";
5
  import { LanguageSelector } from "./LanguageSelector";
6
  import { useTranslation } from "@/hooks/useTranslation";
7
+ import { ContestSection } from "./welcome/ContestSection";
8
+ import { HuggingFaceLink } from "./welcome/HuggingFaceLink";
9
+ import { MainActions } from "./welcome/MainActions";
10
+ import { HowToPlayDialog } from "./welcome/HowToPlayDialog";
11
 
12
  interface WelcomeScreenProps {
13
  onStart: () => void;
 
17
  const [showHighScores, setShowHighScores] = useState(false);
18
  const [showHowToPlay, setShowHowToPlay] = useState(false);
19
  const t = useTranslation();
 
20
 
21
  return (
22
  <>
 
34
  </p>
35
  </div>
36
 
37
+ <MainActions
38
+ onStart={onStart}
39
+ onShowHowToPlay={() => setShowHowToPlay(true)}
40
+ onShowHighScores={() => setShowHighScores(true)}
41
+ />
42
+ </motion.div>
43
+
44
+ <motion.div
45
+ initial={{ opacity: 0 }}
46
+ animate={{ opacity: 1 }}
47
+ transition={{ delay: 0.1 }}
48
+ className="max-w-2xl mx-auto text-center mt-8"
49
+ >
50
+ <ContestSection />
 
 
 
 
 
 
 
 
 
 
51
  </motion.div>
52
 
53
  <motion.div
54
  initial={{ opacity: 0 }}
55
  animate={{ opacity: 1 }}
56
  transition={{ delay: 0.2 }}
57
+ className="max-w-2xl mx-auto text-center mt-8"
58
  >
59
+ <HuggingFaceLink />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  </motion.div>
61
 
62
  <Dialog open={showHighScores} onOpenChange={setShowHighScores}>
63
  <DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-[600px]">
64
  <HighScoreBoard
65
+ showThemeFilter={true}
 
66
  onClose={() => setShowHighScores(false)}
67
  onPlayAgain={onStart}
 
68
  />
69
  </DialogContent>
70
  </Dialog>
71
 
72
+ <HowToPlayDialog
73
+ open={showHowToPlay}
74
+ onOpenChange={setShowHowToPlay}
75
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  </>
77
  );
78
+ };
src/components/game/guess-display/ActionButtons.tsx CHANGED
@@ -1,8 +1,8 @@
1
  import { Button } from "@/components/ui/button";
2
- import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
3
- import { HighScoreBoard } from "@/components/HighScoreBoard";
4
  import { useState } from "react";
5
  import { useTranslation } from "@/hooks/useTranslation";
 
6
 
7
  interface ActionButtonsProps {
8
  isCorrect: boolean;
@@ -11,7 +11,9 @@ interface ActionButtonsProps {
11
  currentScore: number;
12
  avgWordsPerRound: number;
13
  sessionId: string;
14
- onScoreSubmitted: () => void;
 
 
15
  }
16
 
17
  export const ActionButtons = ({
@@ -21,52 +23,54 @@ export const ActionButtons = ({
21
  currentScore,
22
  avgWordsPerRound,
23
  sessionId,
 
24
  onScoreSubmitted,
 
25
  }: ActionButtonsProps) => {
26
- const [isDialogOpen, setIsDialogOpen] = useState(false);
27
  const t = useTranslation();
28
 
 
 
 
 
 
 
 
 
 
 
29
  return (
30
- <div className="flex flex-col gap-4">
31
- {isCorrect ? (
32
- <Button
33
- onClick={onNextRound}
34
- className="w-full bg-primary text-lg hover:bg-primary/90"
35
- >
36
- {t.guess.nextRound}
37
- </Button>
38
- ) : (
39
- <>
40
- <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
41
- <DialogTrigger asChild>
42
- <Button
43
- className="w-full bg-secondary text-lg hover:bg-secondary/90"
44
- >
45
- {t.guess.viewLeaderboard} 🏆
46
- </Button>
47
- </DialogTrigger>
48
- <DialogContent className="max-w-md bg-white">
49
- <HighScoreBoard
50
- currentScore={currentScore}
51
- avgWordsPerRound={avgWordsPerRound}
52
- onClose={() => setIsDialogOpen(false)}
53
- onPlayAgain={() => {
54
- setIsDialogOpen(false);
55
- onPlayAgain();
56
- }}
57
- sessionId={sessionId}
58
- onScoreSubmitted={onScoreSubmitted}
59
- />
60
- </DialogContent>
61
- </Dialog>
62
- <Button
63
- onClick={onPlayAgain}
64
- className="w-full bg-primary text-lg hover:bg-primary/90"
65
- >
66
- {t.guess.playAgain} ⏎
67
- </Button>
68
- </>
69
- )}
70
- </div>
71
  );
72
  };
 
1
  import { Button } from "@/components/ui/button";
2
+ import { Dialog, DialogContent } from "@/components/ui/dialog";
 
3
  import { useState } from "react";
4
  import { useTranslation } from "@/hooks/useTranslation";
5
+ import { HighScoreBoard } from "../../HighScoreBoard";
6
 
7
  interface ActionButtonsProps {
8
  isCorrect: boolean;
 
11
  currentScore: number;
12
  avgWordsPerRound: number;
13
  sessionId: string;
14
+ currentTheme: string;
15
+ onScoreSubmitted?: () => void;
16
+ onHighScoreDialogChange?: (isOpen: boolean) => void;
17
  }
18
 
19
  export const ActionButtons = ({
 
23
  currentScore,
24
  avgWordsPerRound,
25
  sessionId,
26
+ currentTheme,
27
  onScoreSubmitted,
28
+ onHighScoreDialogChange,
29
  }: ActionButtonsProps) => {
30
+ const [showHighScores, setShowHighScores] = useState(false);
31
  const t = useTranslation();
32
 
33
+ const handleShowHighScores = () => {
34
+ setShowHighScores(true);
35
+ onHighScoreDialogChange?.(true);
36
+ };
37
+
38
+ const handleCloseHighScores = () => {
39
+ setShowHighScores(false);
40
+ onHighScoreDialogChange?.(false);
41
+ };
42
+
43
  return (
44
+ <>
45
+ <div className="flex justify-center gap-4">
46
+ {isCorrect ? (
47
+ <Button onClick={onNextRound} className="text-white">{t.game.nextRound} ⏎</Button>
48
+ ) : (
49
+ <>
50
+ <Button onClick={onPlayAgain} className="text-white">
51
+ {t.game.playAgain} ⏎
52
+ </Button>
53
+ <Button onClick={handleShowHighScores} variant="secondary" className="text-white">
54
+ {t.game.saveScore}
55
+ </Button>
56
+ </>
57
+ )}
58
+ </div>
59
+
60
+ <Dialog open={showHighScores} onOpenChange={handleCloseHighScores}>
61
+ <DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-[600px]">
62
+ <HighScoreBoard
63
+ currentScore={currentScore}
64
+ avgWordsPerRound={avgWordsPerRound}
65
+ onClose={handleCloseHighScores}
66
+ onPlayAgain={onPlayAgain}
67
+ sessionId={sessionId}
68
+ showThemeFilter={false}
69
+ initialTheme={currentTheme}
70
+ onScoreSubmitted={onScoreSubmitted}
71
+ />
72
+ </DialogContent>
73
+ </Dialog>
74
+ </>
 
 
 
 
 
 
 
 
 
 
75
  );
76
  };
src/components/game/leaderboard/LeaderboardHeader.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useTranslation } from "@/hooks/useTranslation";
2
+
3
+ interface LeaderboardHeaderProps {
4
+ currentScore?: number;
5
+ avgWordsPerRound?: number;
6
+ showScoreInfo: boolean;
7
+ }
8
+
9
+ export const LeaderboardHeader = ({ currentScore, avgWordsPerRound, showScoreInfo }: LeaderboardHeaderProps) => {
10
+ const t = useTranslation();
11
+
12
+ return (
13
+ <div className="text-center">
14
+ <h2 className="text-2xl font-bold mb-2">{t.leaderboard.title}</h2>
15
+ {showScoreInfo && currentScore !== undefined && (
16
+ <p className="text-gray-600">
17
+ {t.leaderboard.yourScore}: {currentScore} {t.leaderboard.roundCount}
18
+ {currentScore > 0 &&
19
+ avgWordsPerRound !== undefined &&
20
+ ` (${avgWordsPerRound.toFixed(1)} ${t.leaderboard.wordsPerRound})`}
21
+ </p>
22
+ )}
23
+ </div>
24
+ );
25
+ };
src/components/game/leaderboard/LeaderboardPagination.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Pagination,
3
+ PaginationContent,
4
+ PaginationItem,
5
+ PaginationLink,
6
+ PaginationNext,
7
+ PaginationPrevious,
8
+ } from "@/components/ui/pagination";
9
+ import { ChevronLeft, ChevronRight } from "lucide-react";
10
+ import { useTranslation } from "@/hooks/useTranslation";
11
+
12
+ interface LeaderboardPaginationProps {
13
+ currentPage: number;
14
+ totalPages: number;
15
+ onPreviousPage: () => void;
16
+ onNextPage: () => void;
17
+ }
18
+
19
+ export const LeaderboardPagination = ({
20
+ currentPage,
21
+ totalPages,
22
+ onPreviousPage,
23
+ onNextPage,
24
+ }: LeaderboardPaginationProps) => {
25
+ const t = useTranslation();
26
+
27
+ if (totalPages <= 1) return null;
28
+
29
+ return (
30
+ <Pagination>
31
+ <PaginationContent>
32
+ <PaginationItem>
33
+ <PaginationPrevious
34
+ onClick={onPreviousPage}
35
+ className={currentPage === 1 ? "pointer-events-none opacity-50" : ""}
36
+ >
37
+ <ChevronLeft className="h-4 w-4" />
38
+ <span className="sr-only">{t.leaderboard.previous}</span>
39
+ </PaginationPrevious>
40
+ </PaginationItem>
41
+ <PaginationItem>
42
+ <PaginationLink isActive={true}>
43
+ {currentPage}
44
+ </PaginationLink>
45
+ </PaginationItem>
46
+ <PaginationItem>
47
+ <PaginationNext
48
+ onClick={onNextPage}
49
+ className={currentPage === totalPages ? "pointer-events-none opacity-50" : ""}
50
+ >
51
+ <span className="sr-only">{t.leaderboard.next}</span>
52
+ <ChevronRight className="h-4 w-4" />
53
+ </PaginationNext>
54
+ </PaginationItem>
55
+ </PaginationContent>
56
+ </Pagination>
57
+ );
58
+ };
src/components/game/leaderboard/ScoreSubmissionForm.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from "@/components/ui/button";
2
+ import { Input } from "@/components/ui/input";
3
+ import { useTranslation } from "@/hooks/useTranslation";
4
+
5
+ interface ScoreSubmissionFormProps {
6
+ playerName: string;
7
+ setPlayerName: (name: string) => void;
8
+ isSubmitting: boolean;
9
+ hasSubmitted: boolean;
10
+ onSubmit: () => Promise<void>;
11
+ onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
12
+ }
13
+
14
+ export const ScoreSubmissionForm = ({
15
+ playerName,
16
+ setPlayerName,
17
+ isSubmitting,
18
+ hasSubmitted,
19
+ onSubmit,
20
+ onKeyDown,
21
+ }: ScoreSubmissionFormProps) => {
22
+ const t = useTranslation();
23
+
24
+ return (
25
+ <div className="flex gap-4 mb-6">
26
+ <Input
27
+ placeholder={t.leaderboard.enterName}
28
+ value={playerName}
29
+ onChange={(e) => {
30
+ const value = e.target.value.replace(/[^a-zA-ZÀ-ÿ0-9]/g, "");
31
+ setPlayerName(value);
32
+ }}
33
+ onKeyDown={onKeyDown}
34
+ className="flex-1"
35
+ maxLength={20}
36
+ autoComplete="words"
37
+ />
38
+ <Button
39
+ onClick={onSubmit}
40
+ disabled={isSubmitting || !playerName.trim() || hasSubmitted}
41
+ >
42
+ {isSubmitting ? t.leaderboard.submitting : t.leaderboard.submit}
43
+ </Button>
44
+ </div>
45
+ );
46
+ };
src/components/game/leaderboard/ScoresTable.tsx ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Table,
3
+ TableBody,
4
+ TableCell,
5
+ TableHead,
6
+ TableHeader,
7
+ TableRow,
8
+ } from "@/components/ui/table";
9
+ import { useTranslation } from "@/hooks/useTranslation";
10
+
11
+ interface HighScore {
12
+ id: string;
13
+ player_name: string;
14
+ score: number;
15
+ avg_words_per_round: number;
16
+ created_at: string;
17
+ session_id: string;
18
+ theme: string;
19
+ }
20
+
21
+ interface ScoresTableProps {
22
+ scores: HighScore[];
23
+ startIndex: number;
24
+ }
25
+
26
+ const getRankMedal = (rank: number) => {
27
+ switch (rank) {
28
+ case 1:
29
+ return "🥇";
30
+ case 2:
31
+ return "🥈";
32
+ case 3:
33
+ return "🥉";
34
+ default:
35
+ return rank;
36
+ }
37
+ };
38
+
39
+ export const ScoresTable = ({ scores, startIndex }: ScoresTableProps) => {
40
+ const t = useTranslation();
41
+
42
+ return (
43
+ <div className="rounded-md border">
44
+ <Table>
45
+ <TableHeader>
46
+ <TableRow>
47
+ <TableHead>{t.leaderboard.rank}</TableHead>
48
+ <TableHead>{t.leaderboard.player}</TableHead>
49
+ <TableHead>{t.leaderboard.roundsColumn}</TableHead>
50
+ <TableHead>{t.leaderboard.avgWords}</TableHead>
51
+ </TableRow>
52
+ </TableHeader>
53
+ <TableBody>
54
+ {scores?.map((score, index) => {
55
+ const absoluteRank = startIndex + index + 1;
56
+ const medal = getRankMedal(absoluteRank);
57
+ return (
58
+ <TableRow key={score.id}>
59
+ <TableCell>{medal}</TableCell>
60
+ <TableCell>{score.player_name}</TableCell>
61
+ <TableCell>{score.score}</TableCell>
62
+ <TableCell>{score.avg_words_per_round.toFixed(1)}</TableCell>
63
+ </TableRow>
64
+ );
65
+ })}
66
+ {!scores?.length && (
67
+ <TableRow>
68
+ <TableCell colSpan={4} className="text-center">
69
+ {t.leaderboard.noScores}
70
+ </TableCell>
71
+ </TableRow>
72
+ )}
73
+ </TableBody>
74
+ </Table>
75
+ </div>
76
+ );
77
+ };
src/components/game/leaderboard/ThemeFilter.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from "@/components/ui/button";
2
+ import { useTranslation } from "@/hooks/useTranslation";
3
+ import { Filter } from "lucide-react";
4
+
5
+ type Theme = 'standard' | 'sports' | 'food' | 'custom';
6
+
7
+ interface ThemeFilterProps {
8
+ selectedTheme: Theme;
9
+ onThemeChange: (theme: Theme) => void;
10
+ }
11
+
12
+ export const ThemeFilter = ({ selectedTheme, onThemeChange }: ThemeFilterProps) => {
13
+ const t = useTranslation();
14
+
15
+ const themes: Theme[] = ['standard', 'sports', 'food', 'custom'];
16
+
17
+ return (
18
+ <div className="flex flex-wrap gap-2 mb-4 items-center">
19
+ <Filter className="h-4 w-4 text-gray-500" />
20
+ {themes.map((theme) => (
21
+ <Button
22
+ key={theme}
23
+ variant={selectedTheme === theme ? "default" : "outline"}
24
+ size="sm"
25
+ onClick={() => onThemeChange(theme)}
26
+ >
27
+ {t.themes[theme]}
28
+ </Button>
29
+ ))}
30
+ </div>
31
+ );
32
+ };
src/components/game/sentence-builder/InputForm.tsx CHANGED
@@ -29,7 +29,6 @@ export const InputForm = ({
29
  const inputRef = useRef<HTMLInputElement>(null);
30
  const t = useTranslation();
31
 
32
- // Focus input on mount and after AI response
33
  useEffect(() => {
34
  if (!isAiThinking) {
35
  setTimeout(() => {
@@ -56,7 +55,6 @@ export const InputForm = ({
56
 
57
  const error = getInputError();
58
 
59
- // Check if there's either something in the sentence or in the input box
60
  const canMakeGuess = (sentence.length > 0 || playerInput.trim().length > 0) &&
61
  !hasMultipleWords && !containsTargetWord && isValidInput && !isAiThinking;
62
 
@@ -72,6 +70,7 @@ export const InputForm = ({
72
  placeholder={t.game.inputPlaceholder}
73
  className={`w-full ${error ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
74
  disabled={isAiThinking}
 
75
  />
76
  {error && (
77
  <p className="text-sm text-red-500 mt-1">
 
29
  const inputRef = useRef<HTMLInputElement>(null);
30
  const t = useTranslation();
31
 
 
32
  useEffect(() => {
33
  if (!isAiThinking) {
34
  setTimeout(() => {
 
55
 
56
  const error = getInputError();
57
 
 
58
  const canMakeGuess = (sentence.length > 0 || playerInput.trim().length > 0) &&
59
  !hasMultipleWords && !containsTargetWord && isValidInput && !isAiThinking;
60
 
 
70
  placeholder={t.game.inputPlaceholder}
71
  className={`w-full ${error ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
72
  disabled={isAiThinking}
73
+ autoComplete="words"
74
  />
75
  {error && (
76
  <p className="text-sm text-red-500 mt-1">
src/components/game/welcome/ContestSection.tsx ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
2
+ import { Info } from "lucide-react";
3
+ import { useTranslation } from "@/hooks/useTranslation";
4
+
5
+ export const ContestSection = () => {
6
+ const t = useTranslation();
7
+
8
+ return (
9
+ <div className="flex flex-col items-center gap-2">
10
+ <p className="text-lg font-semibold text-primary">🕹️ {t.welcome.contest.prize} 🤑</p>
11
+ <Dialog>
12
+ <DialogTrigger asChild>
13
+ <button className="inline-flex items-center text-sm text-primary/80 hover:text-primary">
14
+ {t.welcome.contest.terms} <Info className="h-4 w-4 ml-1" />
15
+ </button>
16
+ </DialogTrigger>
17
+ <DialogContent className="sm:max-w-[425px]">
18
+ <DialogHeader>
19
+ <DialogTitle>{t.welcome.contest.terms}</DialogTitle>
20
+ </DialogHeader>
21
+ <div className="space-y-4">
22
+ <p className="text-sm text-gray-600">{t.welcome.contest.howTo}</p>
23
+ <ul className="text-sm text-gray-600 list-disc list-inside">
24
+ {t.welcome.contest.conditions.map((condition, index) => (
25
+ <li key={index}>{condition}</li>
26
+ ))}
27
+ </ul>
28
+ <div className="space-y-2">
29
+ <p className="text-sm text-gray-600">{t.welcome.contest.deadline}</p>
30
+ <div className="space-y-1">
31
+ <p className="text-sm font-medium text-gray-800">{t.welcome.contest.prizes.title}</p>
32
+ <ul className="text-sm text-gray-600 list-none space-y-1">
33
+ {t.welcome.contest.prizes.list.map((prize, index) => (
34
+ <li key={index}>{prize}</li>
35
+ ))}
36
+ </ul>
37
+ </div>
38
+ <p className="text-sm font-medium text-red-600">{t.welcome.contest.fairPlay}</p>
39
+ </div>
40
+ </div>
41
+ </DialogContent>
42
+ </Dialog>
43
+ </div>
44
+ );
45
+ };
src/components/game/welcome/HowToPlayDialog.tsx ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
2
+ import { useTranslation } from "@/hooks/useTranslation";
3
+
4
+ interface HowToPlayDialogProps {
5
+ open: boolean;
6
+ onOpenChange: (open: boolean) => void;
7
+ }
8
+
9
+ export const HowToPlayDialog = ({ open, onOpenChange }: HowToPlayDialogProps) => {
10
+ const t = useTranslation();
11
+
12
+ return (
13
+ <Dialog open={open} onOpenChange={onOpenChange}>
14
+ <DialogContent className="sm:max-w-[600px]">
15
+ <DialogHeader>
16
+ <DialogTitle className="text-xl font-semibold text-primary">{t.welcome.howToPlay}</DialogTitle>
17
+ </DialogHeader>
18
+ <div className="space-y-6">
19
+ <div className="grid gap-4 text-gray-600">
20
+ <div>
21
+ <h3 className="font-medium text-gray-800">{t.howToPlay.setup.title}</h3>
22
+ <p>{t.howToPlay.setup.description}</p>
23
+ </div>
24
+ <div>
25
+ <h3 className="font-medium text-gray-800">{t.howToPlay.goal.title}</h3>
26
+ <p>{t.howToPlay.goal.description}</p>
27
+ </div>
28
+ <div>
29
+ <h3 className="font-medium text-gray-800">{t.howToPlay.rules.title}</h3>
30
+ <ul className="list-disc list-inside space-y-1">
31
+ {t.howToPlay.rules.items.map((rule, index) => (
32
+ <li key={index}>{rule}</li>
33
+ ))}
34
+ </ul>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </DialogContent>
39
+ </Dialog>
40
+ );
41
+ };
src/components/game/welcome/HuggingFaceLink.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Heart } from "lucide-react";
2
+ import { useTranslation } from "@/hooks/useTranslation";
3
+
4
+ export const HuggingFaceLink = () => {
5
+ const t = useTranslation();
6
+
7
+ return (
8
+ <div className="flex flex-col items-center gap-2">
9
+ <a
10
+ href="https://huggingface.co/spaces/Mistral-AI-Game-Jam/description-improv/tree/main"
11
+ target="_blank"
12
+ rel="noopener noreferrer"
13
+ className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary hover:text-primary/90 transition-colors border border-primary/20 rounded-md hover:border-primary/40"
14
+ >
15
+ <Heart className="w-4 h-4" /> {t.welcome.likeOnHuggingface}
16
+ </a>
17
+ </div>
18
+ );
19
+ };
src/components/game/welcome/MainActions.tsx ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from "@/components/ui/button";
2
+ import { useTranslation } from "@/hooks/useTranslation";
3
+
4
+ interface MainActionsProps {
5
+ onStart: () => void;
6
+ onShowHowToPlay: () => void;
7
+ onShowHighScores: () => void;
8
+ }
9
+
10
+ export const MainActions = ({ onStart, onShowHowToPlay, onShowHighScores }: MainActionsProps) => {
11
+ const t = useTranslation();
12
+
13
+ return (
14
+ <div className="space-y-4">
15
+ <Button
16
+ onClick={onStart}
17
+ className="w-full bg-primary text-lg hover:bg-primary/90"
18
+ >
19
+ {t.welcome.startButton} ⏎
20
+ </Button>
21
+ <div className="grid grid-cols-2 gap-4">
22
+ <Button
23
+ onClick={onShowHowToPlay}
24
+ variant="outline"
25
+ className="text-lg"
26
+ >
27
+ {t.welcome.howToPlay} 📖
28
+ </Button>
29
+ <Button
30
+ onClick={onShowHighScores}
31
+ variant="outline"
32
+ className="text-lg"
33
+ >
34
+ {t.welcome.leaderboard} 🏆
35
+ </Button>
36
+ </div>
37
+ </div>
38
+ );
39
+ };
src/i18n/translations/de.ts CHANGED
@@ -18,85 +18,110 @@ export const de = {
18
  leaveGameDescription: "Dein aktueller Fortschritt geht verloren. Bist du sicher, dass du das Spiel verlassen möchtest?",
19
  cancel: "Abbrechen",
20
  confirm: "Bestätigen",
21
- describeWord: "Dein Ziel ist es folgendes Wort zu beschreiben"
 
 
 
22
  },
23
- leaderboard: {
24
- title: "Bestenliste",
25
- yourScore: "Deine Punktzahl",
26
- roundCount: "Runden",
27
- wordsPerRound: "Wörter pro Runde",
28
- enterName: "Gib deinen Namen ein",
29
- submitting: "Wird übermittelt...",
30
- submit: "Punktzahl einreichen",
31
- rank: "Rang",
32
- player: "Spieler",
33
- roundsColumn: "Runden",
34
- avgWords: "Ø Wörter",
35
- noScores: "Noch keine Punktzahlen",
36
- previous: "Vorherige",
37
- next: "Nächste",
38
- error: {
39
- invalidName: "Bitte gib einen gültigen Namen ein",
40
- noRounds: "Du musst mindestens eine Runde abschließen",
41
- alreadySubmitted: "Punktzahl bereits eingereicht",
42
- newHighScore: "Neuer Highscore!",
43
- beatRecord: "Du hast deinen bisherigen Rekord von {score} geschlagen!",
44
- notHigher: "Punktzahl von {current} nicht höher als dein Bester von {best}",
45
- submitError: "Fehler beim Einreichen der Punktzahl"
46
- }
47
- },
48
- guess: {
49
- title: "KI-Vermutung",
50
- goalDescription: "Dein Ziel war es folgendes Wort zu beschreiben",
51
- providedDescription: "Du hast folgende Beschreibung gegeben",
52
- aiGuessedDescription: "Basierend auf deiner Beschreibung hat die KI geraten",
53
- correct: "Das ist richtig!",
54
- incorrect: "Das ist falsch.",
55
- nextRound: "Nächste Runde",
56
- playAgain: "Erneut spielen",
57
- viewLeaderboard: "In Bestenliste eintragen",
58
- cheatingDetected: "Betrugsversuch erkannt!"
59
- },
60
- themes: {
61
- title: "Wähle ein Thema",
62
- subtitle: "Wähle ein Thema für das Wort, das die KI erraten soll",
63
- standard: "Standard",
64
- technology: "Technologie",
65
- sports: "Sport",
66
- food: "Essen",
67
- custom: "Benutzerdefiniertes Thema",
68
- customPlaceholder: "Gib dein eigenes Thema ein...",
69
- continue: "Weiter",
70
- generating: "Wird generiert...",
71
- pressKey: "Drücke"
72
- },
73
- welcome: {
74
- title: "Think in Sync",
75
- subtitle: "Arbeite mit einer KI zusammen, um einen Hinweis zu erstellen, und lass eine andere KI dein geheimes Wort erraten!",
76
- startButton: "Spiel starten",
77
- howToPlay: "Spielanleitung",
78
- leaderboard: "Bestenliste",
79
- credits: "Erstellt während des",
80
- helpWin: "Hilf uns zu gewinnen",
81
- onHuggingface: "mit einem Like auf Huggingface"
82
- },
83
- howToPlay: {
84
- setup: {
85
- title: "Vorbereitung",
86
- description: "Wähle ein Thema und erhalte ein geheimes Wort, das die KI erraten soll."
87
  },
88
- goal: {
89
- title: "Ziel",
90
- description: "Baue gemeinsam mit der KI Sätze, die dein Wort beschreiben, ohne es direkt zu verwenden."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  },
92
- rules: {
93
- title: "Regeln",
94
- items: [
95
- "Füge abwechselnd Wörter hinzu, um beschreibende Sätze zu bilden",
96
- "Verwende nicht das geheime Wort oder seine Variationen",
97
- "Sei kreativ und beschreibend",
98
- "Die KI wird nach jedem Satz versuchen, dein Wort zu erraten"
99
- ]
 
 
 
 
 
 
 
 
 
 
100
  }
101
- }
102
- };
 
18
  leaveGameDescription: "Dein aktueller Fortschritt geht verloren. Bist du sicher, dass du das Spiel verlassen möchtest?",
19
  cancel: "Abbrechen",
20
  confirm: "Bestätigen",
21
+ describeWord: "Dein Ziel ist es folgendes Wort zu beschreiben",
22
+ nextRound: "Nächste Runde",
23
+ playAgain: "Erneut spielen",
24
+ saveScore: "Punktzahl speichern"
25
  },
26
+ leaderboard: {
27
+ title: "Bestenliste",
28
+ yourScore: "Deine Punktzahl",
29
+ roundCount: "Runden",
30
+ wordsPerRound: "Wörter pro Runde",
31
+ enterName: "Gib deinen Namen ein",
32
+ submitting: "Wird übermittelt...",
33
+ submit: "Punktzahl einreichen",
34
+ rank: "Rang",
35
+ player: "Spieler",
36
+ roundsColumn: "Runden",
37
+ avgWords: "Durchschn. Wörter",
38
+ noScores: "Noch keine Punktzahlen",
39
+ previous: "Vorherige",
40
+ next: "Nächste",
41
+ success: "Punktzahl erfolgreich übermittelt!",
42
+ error: {
43
+ invalidName: "Bitte gib einen gültigen Namen ein",
44
+ noRounds: "Du musst mindestens eine Runde abschließen",
45
+ alreadySubmitted: "Punktzahl bereits eingereicht",
46
+ newHighScore: "Neuer Highscore!",
47
+ beatRecord: "Du hast deinen bisherigen Rekord von {score} geschlagen!",
48
+ notHigher: "Punktzahl von {current} nicht höher als dein Bester von {best}",
49
+ submitError: "Fehler beim Einreichen der Punktzahl"
50
+ }
51
+ },
52
+ guess: {
53
+ title: "KI-Vermutung",
54
+ goalDescription: "Dein Ziel war es folgendes Wort zu beschreiben",
55
+ providedDescription: "Du hast folgende Beschreibung gegeben",
56
+ aiGuessedDescription: "Basierend auf deiner Beschreibung hat die KI geraten",
57
+ correct: "Das ist richtig!",
58
+ incorrect: "Das ist falsch.",
59
+ nextRound: "Nächste Runde",
60
+ playAgain: "Erneut spielen",
61
+ viewLeaderboard: "In Bestenliste eintragen",
62
+ cheatingDetected: "Betrugsversuch erkannt!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  },
64
+ themes: {
65
+ title: "Wähle ein Thema",
66
+ subtitle: "Wähle ein Thema für das Wort, das die KI erraten soll",
67
+ standard: "Standard",
68
+ technology: "Technologie",
69
+ sports: "Sport",
70
+ food: "Essen",
71
+ custom: "Eigenes Thema",
72
+ customPlaceholder: "Gib dein eigenes Thema ein...",
73
+ continue: "Weiter",
74
+ generating: "Wird generiert...",
75
+ pressKey: "Drücke",
76
+ playing: "Thema"
77
+ },
78
+ welcome: {
79
+ title: "Think in Sync",
80
+ subtitle: "Arbeite mit KI zusammen, um einen Hinweis zu erstellen und lass eine andere KI dein geheimes Wort erraten!",
81
+ startButton: "Spiel starten",
82
+ howToPlay: "Spielanleitung",
83
+ leaderboard: "Bestenliste",
84
+ credits: "Erstellt während des",
85
+ contest: {
86
+ prize: "Spiele und gewinne bis zu 50€!",
87
+ terms: "Bedingungen ansehen",
88
+ howTo: "So nimmst du teil:",
89
+ conditions: [
90
+ "Spiele Think in Sync mit der Standard-Wortliste",
91
+ "Setze deinen Bestenlisten-Namen gleich deinem Hugging Face Benutzernamen",
92
+ "Like unser Projekt auf Hugging Face"
93
+ ],
94
+ deadline: "Ende: 5. Februar, 10:00 Uhr",
95
+ prizes: {
96
+ title: "Kämpfe um die Top 5 Plätze und gewinne:",
97
+ list: [
98
+ "🥇 1. Platz: 50€",
99
+ "🥈 2. Platz: 20€",
100
+ "🥉 3. Platz: 10€",
101
+ "🎖️ 4. & 5. Platz: je 10€"
102
+ ]
103
+ },
104
+ fairPlay: "🚨 Faires Spielen wird überwacht. Betrug führt zur Disqualifikation!"
105
+ },
106
+ likeOnHuggingface: "Auf Hugging Face liken"
107
  },
108
+ howToPlay: {
109
+ setup: {
110
+ title: "Vorbereitung",
111
+ description: "Wähle ein Thema und erhalte ein geheimes Wort, das die KI erraten soll."
112
+ },
113
+ goal: {
114
+ title: "Ziel",
115
+ description: "Baue gemeinsam mit der KI Sätze, die dein Wort beschreiben, ohne es direkt zu verwenden."
116
+ },
117
+ rules: {
118
+ title: "Regeln",
119
+ items: [
120
+ "Füge abwechselnd Wörter hinzu, um beschreibende Sätze zu bilden",
121
+ "Verwende nicht das geheime Wort oder seine Variationen",
122
+ "Sei kreativ und beschreibend",
123
+ "Die KI wird nach jedem Satz versuchen, dein Wort zu erraten"
124
+ ]
125
+ }
126
  }
127
+ };
 
src/i18n/translations/en.ts CHANGED
@@ -18,7 +18,10 @@ export const en = {
18
  leaveGameDescription: "Your current progress will be lost. Are you sure you want to leave?",
19
  cancel: "Cancel",
20
  confirm: "Confirm",
21
- describeWord: "Your goal is to describe the word"
 
 
 
22
  },
23
  leaderboard: {
24
  title: "High Scores",
@@ -31,10 +34,11 @@ export const en = {
31
  rank: "Rank",
32
  player: "Player",
33
  roundsColumn: "Rounds",
34
- avgWords: "Ø Words",
35
  noScores: "No scores yet",
36
  previous: "Previous",
37
  next: "Next",
 
38
  error: {
39
  invalidName: "Please enter a valid name",
40
  noRounds: "You need to complete at least one round",
@@ -59,7 +63,7 @@ export const en = {
59
  },
60
  themes: {
61
  title: "Choose a Theme",
62
- subtitle: "Select a theme for the word the AI will try to guess",
63
  standard: "Standard",
64
  technology: "Technology",
65
  sports: "Sports",
@@ -68,7 +72,8 @@ export const en = {
68
  customPlaceholder: "Enter your custom theme...",
69
  continue: "Continue",
70
  generating: "Generating...",
71
- pressKey: "Press"
 
72
  },
73
  welcome: {
74
  title: "Think in Sync",
@@ -77,8 +82,28 @@ export const en = {
77
  howToPlay: "How to Play",
78
  leaderboard: "Leaderboard",
79
  credits: "Created during the",
80
- helpWin: "Help us win by",
81
- onHuggingface: "Liking on Huggingface"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  },
83
  howToPlay: {
84
  setup: {
@@ -99,4 +124,4 @@ export const en = {
99
  ]
100
  }
101
  }
102
- };
 
18
  leaveGameDescription: "Your current progress will be lost. Are you sure you want to leave?",
19
  cancel: "Cancel",
20
  confirm: "Confirm",
21
+ describeWord: "Your goal is to describe the word",
22
+ nextRound: "Next Round",
23
+ playAgain: "Play Again",
24
+ saveScore: "Save Score"
25
  },
26
  leaderboard: {
27
  title: "High Scores",
 
34
  rank: "Rank",
35
  player: "Player",
36
  roundsColumn: "Rounds",
37
+ avgWords: "Avg. Words",
38
  noScores: "No scores yet",
39
  previous: "Previous",
40
  next: "Next",
41
+ success: "Score submitted successfully!",
42
  error: {
43
  invalidName: "Please enter a valid name",
44
  noRounds: "You need to complete at least one round",
 
63
  },
64
  themes: {
65
  title: "Choose a Theme",
66
+ subtitle: "Select a theme for the word that the AI will try to guess",
67
  standard: "Standard",
68
  technology: "Technology",
69
  sports: "Sports",
 
72
  customPlaceholder: "Enter your custom theme...",
73
  continue: "Continue",
74
  generating: "Generating...",
75
+ pressKey: "Press",
76
+ playing: "Theme"
77
  },
78
  welcome: {
79
  title: "Think in Sync",
 
82
  howToPlay: "How to Play",
83
  leaderboard: "Leaderboard",
84
  credits: "Created during the",
85
+ contest: {
86
+ prize: "Play to win up to 50€!",
87
+ terms: "See Terms",
88
+ howTo: "How to participate:",
89
+ conditions: [
90
+ "Play Think in Sync using the Standard wordlist",
91
+ "Set your leaderboard name to match your Hugging Face username",
92
+ "Like our project on Hugging Face"
93
+ ],
94
+ deadline: "Ends: February 5, 10:00 AM",
95
+ prizes: {
96
+ title: "Compete for the top 5 spots and win:",
97
+ list: [
98
+ "🥇 1st: 50€",
99
+ "🥈 2nd: 20€",
100
+ "🥉 3rd: 10€",
101
+ "🎖️ 4th & 5th: 10€ each"
102
+ ]
103
+ },
104
+ fairPlay: "🚨 Fair play is monitored. Any cheating will result in disqualification!"
105
+ },
106
+ likeOnHuggingface: "Like on Hugging Face"
107
  },
108
  howToPlay: {
109
  setup: {
 
124
  ]
125
  }
126
  }
127
+ };
src/i18n/translations/es.ts CHANGED
@@ -18,7 +18,10 @@ export const es = {
18
  leaveGameDescription: "Tu progreso actual se perderá. ¿Estás seguro de que quieres salir?",
19
  cancel: "Cancelar",
20
  confirm: "Confirmar",
21
- describeWord: "Tu objetivo es describir la palabra"
 
 
 
22
  },
23
  leaderboard: {
24
  title: "Puntuaciones Más Altas",
@@ -31,10 +34,11 @@ export const es = {
31
  rank: "Posición",
32
  player: "Jugador",
33
  roundsColumn: "Rondas",
34
- avgWords: "Ø Palabras",
35
  noScores: "Aún no hay puntuaciones",
36
  previous: "Anterior",
37
  next: "Siguiente",
 
38
  error: {
39
  invalidName: "Por favor, ingresa un nombre válido",
40
  noRounds: "Debes completar al menos una ronda",
@@ -68,17 +72,38 @@ export const es = {
68
  customPlaceholder: "Ingresa tu tema personalizado...",
69
  continue: "Continuar",
70
  generating: "Generando...",
71
- pressKey: "Presiona"
 
72
  },
73
  welcome: {
74
  title: "Think in Sync",
75
- subtitle: "¡Haz equipo con una IA para crear una pista y deja que otra IA adivine tu palabra secreta!",
76
- startButton: "Comenzar Juego",
77
- howToPlay: "Cómo Jugar",
78
- leaderboard: "Clasificación",
79
  credits: "Creado durante el",
80
- helpWin: "Ayúdanos a ganar",
81
- onHuggingface: "Dando me gusta en Huggingface"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  },
83
  howToPlay: {
84
  setup: {
@@ -99,4 +124,4 @@ export const es = {
99
  ]
100
  }
101
  }
102
- };
 
18
  leaveGameDescription: "Tu progreso actual se perderá. ¿Estás seguro de que quieres salir?",
19
  cancel: "Cancelar",
20
  confirm: "Confirmar",
21
+ describeWord: "Tu objetivo es describir la palabra",
22
+ nextRound: "Siguiente Ronda",
23
+ playAgain: "Jugar de Nuevo",
24
+ saveScore: "Guardar Puntuación"
25
  },
26
  leaderboard: {
27
  title: "Puntuaciones Más Altas",
 
34
  rank: "Posición",
35
  player: "Jugador",
36
  roundsColumn: "Rondas",
37
+ avgWords: "Prom. Palabras",
38
  noScores: "Aún no hay puntuaciones",
39
  previous: "Anterior",
40
  next: "Siguiente",
41
+ success: "¡Puntuación enviada con éxito!",
42
  error: {
43
  invalidName: "Por favor, ingresa un nombre válido",
44
  noRounds: "Debes completar al menos una ronda",
 
72
  customPlaceholder: "Ingresa tu tema personalizado...",
73
  continue: "Continuar",
74
  generating: "Generando...",
75
+ pressKey: "Presiona",
76
+ playing: "Tema"
77
  },
78
  welcome: {
79
  title: "Think in Sync",
80
+ subtitle: "¡Forma equipo con la IA para crear una pista y deja que otra IA adivine tu palabra secreta!",
81
+ startButton: "Comenzar juego",
82
+ howToPlay: "Cómo jugar",
83
+ leaderboard: "Tabla de clasificación",
84
  credits: "Creado durante el",
85
+ contest: {
86
+ prize: "¡Juega para ganar hasta 50€!",
87
+ terms: "Ver términos",
88
+ howTo: "Cómo participar:",
89
+ conditions: [
90
+ "Juega Think in Sync usando la lista de palabras estándar",
91
+ "Establece tu nombre en la tabla de clasificación igual a tu nombre de usuario de Hugging Face",
92
+ "Dale me gusta a nuestro proyecto en Hugging Face"
93
+ ],
94
+ deadline: "Finaliza: 5 de febrero, 10:00 AM",
95
+ prizes: {
96
+ title: "Compite por los 5 primeros puestos y gana:",
97
+ list: [
98
+ "🥇 1º: 50€",
99
+ "🥈 2º: 20€",
100
+ "🥉 3º: 10€",
101
+ "🎖️ 4º y 5º: 10€ cada uno"
102
+ ]
103
+ },
104
+ fairPlay: "🚨 El juego limpio está monitoreado. ¡Cualquier trampa resultará en descalificación!"
105
+ },
106
+ likeOnHuggingface: "Me gusta en Hugging Face"
107
  },
108
  howToPlay: {
109
  setup: {
 
124
  ]
125
  }
126
  }
127
+ };
src/i18n/translations/fr.ts CHANGED
@@ -17,85 +17,110 @@ export const fr = {
17
  leaveGameDescription: "Votre progression actuelle sera perdue. Êtes-vous sûr de vouloir quitter ?",
18
  cancel: "Annuler",
19
  confirm: "Confirmer",
20
- describeWord: "Votre objectif est de décrire le mot"
 
 
 
21
  },
22
- leaderboard: {
23
- title: "Meilleurs Scores",
24
- yourScore: "Votre Score",
25
- roundCount: "tours",
26
- wordsPerRound: "mots par tour",
27
- enterName: "Entrez votre nom",
28
- submitting: "Envoi en cours...",
29
- submit: "Soumettre le Score",
30
- rank: "Rang",
31
- player: "Joueur",
32
- roundsColumn: "Tours",
33
- avgWords: "Ø Mots",
34
- noScores: "Pas encore de scores",
35
- previous: "Précédent",
36
- next: "Suivant",
37
- error: {
38
- invalidName: "Veuillez entrer un nom valide",
39
- noRounds: "Vous devez compléter au moins un tour",
40
- alreadySubmitted: "Score déjà soumis",
41
- newHighScore: "Nouveau Record !",
42
- beatRecord: "Vous avez battu votre record précédent de {score} !",
43
- notHigher: "Score de {current} pas plus élevé que votre meilleur de {best}",
44
- submitError: "Erreur lors de la soumission du score"
45
- }
46
- },
47
- guess: {
48
- title: "Devinette de l'IA",
49
- goalDescription: "Votre objectif était de décrire le mot",
50
- providedDescription: "Vous avez fourni la description",
51
- aiGuessedDescription: "Basé sur votre description, l'IA a deviné",
52
- correct: "C'est correct !",
53
- incorrect: "C'est incorrect.",
54
- nextRound: "Tour Suivant",
55
- playAgain: "Rejouer",
56
- viewLeaderboard: "Voir les Scores",
57
- cheatingDetected: "Tentative de triche détectée !"
58
- },
59
- themes: {
60
- title: "Choisissez un Thème",
61
- subtitle: "Sélectionnez un thème pour le mot que l'IA essaiera de deviner",
62
- standard: "Standard",
63
- technology: "Technologie",
64
- sports: "Sports",
65
- food: "Nourriture",
66
- custom: "Thème Personnalisé",
67
- customPlaceholder: "Entrez votre thème personnalisé...",
68
- continue: "Continuer",
69
- generating: "Génération...",
70
- pressKey: "Appuyez sur"
71
- },
72
- welcome: {
73
- title: "Think in Sync",
74
- subtitle: "Faites équipe avec une IA pour créer un indice et laissez une autre IA deviner votre mot secret",
75
- startButton: "Commencer",
76
- howToPlay: "Comment Jouer",
77
- leaderboard: "Classement",
78
- credits: "Créé pendant le",
79
- helpWin: "Aidez-nous à gagner en",
80
- onHuggingface: "Nous aimant sur Huggingface"
81
- },
82
- howToPlay: {
83
- setup: {
84
- title: "Mise en place",
85
- description: "Choisissez un thème et obtenez un mot secret que l'IA essaiera de deviner."
86
  },
87
- goal: {
88
- title: "Objectif",
89
- description: "Construisez des phrases avec l'IA qui décrivent votre mot sans l'utiliser directement."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  },
91
- rules: {
92
- title: "Règles",
93
- items: [
94
- "Ajoutez des mots à tour de rôle pour construire des phrases descriptives",
95
- "N'utilisez pas le mot secret ou ses variations",
96
- "Soyez créatif et descriptif",
97
- "L'IA essaiera de deviner votre mot après chaque phrase"
98
- ]
 
 
 
 
 
 
 
 
 
 
99
  }
100
- }
101
- };
 
17
  leaveGameDescription: "Votre progression actuelle sera perdue. Êtes-vous sûr de vouloir quitter ?",
18
  cancel: "Annuler",
19
  confirm: "Confirmer",
20
+ describeWord: "Votre objectif est de décrire le mot",
21
+ nextRound: "Tour Suivant",
22
+ playAgain: "Rejouer",
23
+ saveScore: "Sauvegarder le Score"
24
  },
25
+ leaderboard: {
26
+ title: "Meilleurs Scores",
27
+ yourScore: "Votre Score",
28
+ roundCount: "tours",
29
+ wordsPerRound: "mots par tour",
30
+ enterName: "Entrez votre nom",
31
+ submitting: "Envoi en cours...",
32
+ submit: "Soumettre le Score",
33
+ rank: "Rang",
34
+ player: "Joueur",
35
+ roundsColumn: "Tours",
36
+ avgWords: "Moy. Mots",
37
+ noScores: "Pas encore de scores",
38
+ previous: "Précédent",
39
+ next: "Suivant",
40
+ success: "Score soumis avec succès !",
41
+ error: {
42
+ invalidName: "Veuillez entrer un nom valide",
43
+ noRounds: "Vous devez compléter au moins un tour",
44
+ alreadySubmitted: "Score déjà soumis",
45
+ newHighScore: "Nouveau Record !",
46
+ beatRecord: "Vous avez battu votre record précédent de {score} !",
47
+ notHigher: "Score de {current} pas plus élevé que votre meilleur de {best}",
48
+ submitError: "Erreur lors de la soumission du score"
49
+ }
50
+ },
51
+ guess: {
52
+ title: "Devinette de l'IA",
53
+ goalDescription: "Votre objectif était de décrire le mot",
54
+ providedDescription: "Vous avez fourni la description",
55
+ aiGuessedDescription: "Basé sur votre description, l'IA a deviné",
56
+ correct: "C'est correct !",
57
+ incorrect: "C'est incorrect.",
58
+ nextRound: "Tour Suivant",
59
+ playAgain: "Rejouer",
60
+ viewLeaderboard: "Voir les Scores",
61
+ cheatingDetected: "Tentative de triche détectée !"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  },
63
+ themes: {
64
+ title: "Choisissez un Thème",
65
+ subtitle: "Sélectionnez un thème pour le mot que l'IA essaiera de deviner",
66
+ standard: "Standard",
67
+ technology: "Technologie",
68
+ sports: "Sports",
69
+ food: "Nourriture",
70
+ custom: "Thème Personnalisé",
71
+ customPlaceholder: "Entrez votre thème personnalisé...",
72
+ continue: "Continuer",
73
+ generating: "Génération...",
74
+ pressKey: "Appuyez sur",
75
+ playing: "Thème"
76
+ },
77
+ welcome: {
78
+ title: "Think in Sync",
79
+ subtitle: "Faites équipe avec une IA pour créer un indice et laissez une autre IA deviner votre mot secret !",
80
+ startButton: "Commencer",
81
+ howToPlay: "Comment Jouer",
82
+ leaderboard: "Classement",
83
+ credits: "Créé pendant le",
84
+ contest: {
85
+ prize: "Jouez pour gagner jusqu'à 50€ !",
86
+ terms: "Voir conditions",
87
+ howTo: "Comment participer :",
88
+ conditions: [
89
+ "Jouez à Think in Sync avec la liste de mots standard",
90
+ "Utilisez votre nom d'utilisateur Hugging Face dans le classement",
91
+ "Aimez notre projet sur Hugging Face"
92
+ ],
93
+ deadline: "Fin : 5 février, 10h00",
94
+ prizes: {
95
+ title: "Participez pour les 5 premières places et gagnez :",
96
+ list: [
97
+ "🥇 1er : 50€",
98
+ "🥈 2ème : 20€",
99
+ "🥉 3ème : 10€",
100
+ "🎖️ 4ème & 5ème : 10€ chacun"
101
+ ]
102
+ },
103
+ fairPlay: "🚨 Le fair-play est surveillé. Toute triche entraînera une disqualification !"
104
+ },
105
+ likeOnHuggingface: "Aimer sur Hugging Face"
106
  },
107
+ howToPlay: {
108
+ setup: {
109
+ title: "Mise en place",
110
+ description: "Choisissez un thème et obtenez un mot secret que l'IA essaiera de deviner."
111
+ },
112
+ goal: {
113
+ title: "Objectif",
114
+ description: "Construisez des phrases avec l'IA qui décrivent votre mot sans l'utiliser directement."
115
+ },
116
+ rules: {
117
+ title: "Règles",
118
+ items: [
119
+ "Ajoutez des mots à tour de rôle pour construire des phrases descriptives",
120
+ "N'utilisez pas le mot secret ou ses variations",
121
+ "Soyez créatif et descriptif",
122
+ "L'IA essaiera de deviner votre mot après chaque phrase"
123
+ ]
124
+ }
125
  }
126
+ };
 
src/i18n/translations/it.ts CHANGED
@@ -18,87 +18,112 @@ export const it = {
18
  leaveGameDescription: "I tuoi progressi attuali andranno persi. Sei sicuro di voler uscire?",
19
  cancel: "Annulla",
20
  confirm: "Conferma",
21
- describeWord: "Il tuo obiettivo è descrivere la parola"
 
 
 
22
  },
23
- leaderboard: {
24
- title: "Punteggi Migliori",
25
- yourScore: "Il Tuo Punteggio",
26
- roundCount: "turni",
27
- wordsPerRound: "parole per turno",
28
- enterName: "Inserisci il tuo nome",
29
- submitting: "Invio in corso...",
30
- submit: "Invia Punteggio",
31
- rank: "Posizione",
32
- player: "Giocatore",
33
- roundsColumn: "Turni",
34
- avgWords: "Ø Parole",
35
- noScores: "Ancora nessun punteggio",
36
- previous: "Precedente",
37
- next: "Successivo",
38
- error: {
39
- invalidName: "Inserisci un nome valido",
40
- noRounds: "Devi completare almeno un turno",
41
- alreadySubmitted: "Punteggio già inviato",
42
- newHighScore: "Nuovo Record!",
43
- beatRecord: "Hai battuto il tuo record precedente di {score}!",
44
- notHigher: "Punteggio di {current} non superiore al tuo migliore di {best}",
45
- submitError: "Errore nell'invio del punteggio"
46
- }
47
- },
48
- guess: {
49
- title: "Ipotesi dell'IA",
50
- sentence: "La tua frase",
51
- aiGuessed: "L'IA ha indovinato",
52
- goalDescription: "Il tuo obiettivo era descrivere la parola",
53
- providedDescription: "Hai fornito la descrizione",
54
- aiGuessedDescription: "Basandosi sulla tua descrizione, l'IA ha indovinato",
55
- correct: "Corretto! L'IA ha indovinato la parola!",
56
- incorrect: "Sbagliato. Riprova!",
57
- nextRound: "Prossimo Turno",
58
- playAgain: "Gioca Ancora",
59
- viewLeaderboard: "Vedi Classifica",
60
- cheatingDetected: "Tentativo di imbroglio rilevato!"
61
- },
62
- themes: {
63
- title: "Scegli un Tema",
64
- subtitle: "Seleziona un tema per la parola che l'IA cercherà di indovinare",
65
- standard: "Standard",
66
- technology: "Tecnologia",
67
- sports: "Sport",
68
- food: "Cibo",
69
- custom: "Tema Personalizzato",
70
- customPlaceholder: "Inserisci il tuo tema personalizzato...",
71
- continue: "Continua",
72
- generating: "Generazione...",
73
- pressKey: "Premi"
74
- },
75
- welcome: {
76
- title: "Think in Sync",
77
- subtitle: "Collabora con un'IA per creare un indizio e lascia che un'altra IA indovini la tua parola segreta!",
78
- startButton: "Inizia Gioco",
79
- howToPlay: "Come Giocare",
80
- leaderboard: "Classifica",
81
- credits: "Creato durante il",
82
- helpWin: "Aiutaci a vincere",
83
- onHuggingface: "Mettendo mi piace su Huggingface"
84
- },
85
- howToPlay: {
86
- setup: {
87
- title: "Preparazione",
88
- description: "Scegli un tema e ottieni una parola segreta che l'IA cercherà di indovinare."
89
  },
90
- goal: {
91
- title: "Obiettivo",
92
- description: "Costruisci frasi insieme all'IA che descrivono la tua parola senza usarla direttamente."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  },
94
- rules: {
95
- title: "Regole",
96
- items: [
97
- "Aggiungi parole a turno per costruire frasi descrittive",
98
- "Non usare la parola segreta o le sue variazioni",
99
- "Sii creativo e descrittivo",
100
- "L'IA cercherà di indovinare la tua parola dopo ogni frase"
101
- ]
 
 
 
 
 
 
 
 
 
 
102
  }
103
- }
104
- };
 
18
  leaveGameDescription: "I tuoi progressi attuali andranno persi. Sei sicuro di voler uscire?",
19
  cancel: "Annulla",
20
  confirm: "Conferma",
21
+ describeWord: "Il tuo obiettivo è descrivere la parola",
22
+ nextRound: "Prossimo Turno",
23
+ playAgain: "Gioca Ancora",
24
+ saveScore: "Salva Punteggio"
25
  },
26
+ leaderboard: {
27
+ title: "Punteggi Migliori",
28
+ yourScore: "Il Tuo Punteggio",
29
+ roundCount: "turni",
30
+ wordsPerRound: "parole per turno",
31
+ enterName: "Inserisci il tuo nome",
32
+ submitting: "Invio in corso...",
33
+ submit: "Invia Punteggio",
34
+ rank: "Posizione",
35
+ player: "Giocatore",
36
+ roundsColumn: "Turni",
37
+ avgWords: "Media Parole",
38
+ noScores: "Ancora nessun punteggio",
39
+ previous: "Precedente",
40
+ next: "Successivo",
41
+ success: "Punteggio inviato con successo!",
42
+ error: {
43
+ invalidName: "Inserisci un nome valido",
44
+ noRounds: "Devi completare almeno un turno",
45
+ alreadySubmitted: "Punteggio già inviato",
46
+ newHighScore: "Nuovo Record!",
47
+ beatRecord: "Hai battuto il tuo record precedente di {score}!",
48
+ notHigher: "Punteggio di {current} non superiore al tuo migliore di {best}",
49
+ submitError: "Errore nell'invio del punteggio"
50
+ }
51
+ },
52
+ guess: {
53
+ title: "Ipotesi dell'IA",
54
+ sentence: "La tua frase",
55
+ aiGuessed: "L'IA ha indovinato",
56
+ goalDescription: "Il tuo obiettivo era descrivere la parola",
57
+ providedDescription: "Hai fornito la descrizione",
58
+ aiGuessedDescription: "Basandosi sulla tua descrizione, l'IA ha indovinato",
59
+ correct: "Corretto! L'IA ha indovinato la parola!",
60
+ incorrect: "Sbagliato. Riprova!",
61
+ nextRound: "Prossimo Turno",
62
+ playAgain: "Gioca Ancora",
63
+ viewLeaderboard: "Vedi Classifica",
64
+ cheatingDetected: "Tentativo di imbroglio rilevato!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  },
66
+ themes: {
67
+ title: "Scegli un Tema",
68
+ subtitle: "Seleziona un tema per la parola che l'IA cercherà di indovinare",
69
+ standard: "Standard",
70
+ technology: "Tecnologia",
71
+ sports: "Sport",
72
+ food: "Cibo",
73
+ custom: "Tema Personalizzato",
74
+ customPlaceholder: "Inserisci il tuo tema personalizzato...",
75
+ continue: "Continua",
76
+ generating: "Generazione...",
77
+ pressKey: "Premi",
78
+ playing: "Tema"
79
+ },
80
+ welcome: {
81
+ title: "Think in Sync",
82
+ subtitle: "Fai squadra con l'IA per creare un indizio e lascia che un'altra IA indovini la tua parola segreta!",
83
+ startButton: "Inizia gioco",
84
+ howToPlay: "Come giocare",
85
+ leaderboard: "Classifica",
86
+ credits: "Creato durante il",
87
+ contest: {
88
+ prize: "Gioca per vincere fino a 50€!",
89
+ terms: "Vedi termini",
90
+ howTo: "Come partecipare:",
91
+ conditions: [
92
+ "Gioca a Think in Sync usando la lista di parole standard",
93
+ "Imposta il tuo nome in classifica uguale al tuo nome utente Hugging Face",
94
+ "Metti mi piace al nostro progetto su Hugging Face"
95
+ ],
96
+ deadline: "Termina: 5 febbraio, 10:00",
97
+ prizes: {
98
+ title: "Competi per i primi 5 posti e vinci:",
99
+ list: [
100
+ "🥇 1°: 50€",
101
+ "🥈 2°: 20€",
102
+ "🥉 3°: 10€",
103
+ "🎖️ 4° e 5°: 10€ ciascuno"
104
+ ]
105
+ },
106
+ fairPlay: "🚨 Il fair play è monitorato. Qualsiasi imbroglio porterà alla squalifica!"
107
+ },
108
+ likeOnHuggingface: "Mi piace su Hugging Face"
109
  },
110
+ howToPlay: {
111
+ setup: {
112
+ title: "Preparazione",
113
+ description: "Scegli un tema e ottieni una parola segreta che l'IA cercherà di indovinare."
114
+ },
115
+ goal: {
116
+ title: "Obiettivo",
117
+ description: "Costruisci frasi insieme all'IA che descrivono la tua parola senza usarla direttamente."
118
+ },
119
+ rules: {
120
+ title: "Regole",
121
+ items: [
122
+ "Aggiungi parole a turno per costruire frasi descrittive",
123
+ "Non usare la parola segreta o le sue variazioni",
124
+ "Sii creativo e descrittivo",
125
+ "L'IA cercherà di indovinare la tua parola dopo ogni frase"
126
+ ]
127
+ }
128
  }
129
+ };
 
src/integrations/supabase/types.ts CHANGED
@@ -47,6 +47,7 @@ export type Database = {
47
  player_name: string
48
  score: number
49
  session_id: string
 
50
  }
51
  Insert: {
52
  avg_words_per_round: number
@@ -55,6 +56,7 @@ export type Database = {
55
  player_name: string
56
  score: number
57
  session_id: string
 
58
  }
59
  Update: {
60
  avg_words_per_round?: number
@@ -63,6 +65,7 @@ export type Database = {
63
  player_name?: string
64
  score?: number
65
  session_id?: string
 
66
  }
67
  Relationships: []
68
  }
@@ -71,7 +74,16 @@ export type Database = {
71
  [_ in never]: never
72
  }
73
  Functions: {
74
- [_ in never]: never
 
 
 
 
 
 
 
 
 
75
  }
76
  Enums: {
77
  [_ in never]: never
 
47
  player_name: string
48
  score: number
49
  session_id: string
50
+ theme: string
51
  }
52
  Insert: {
53
  avg_words_per_round: number
 
56
  player_name: string
57
  score: number
58
  session_id: string
59
+ theme?: string
60
  }
61
  Update: {
62
  avg_words_per_round?: number
 
65
  player_name?: string
66
  score?: number
67
  session_id?: string
68
+ theme?: string
69
  }
70
  Relationships: []
71
  }
 
74
  [_ in never]: never
75
  }
76
  Functions: {
77
+ check_and_update_high_score: {
78
+ Args: {
79
+ p_player_name: string
80
+ p_score: number
81
+ p_avg_words_per_round: number
82
+ p_session_id: string
83
+ p_theme?: string
84
+ }
85
+ Returns: boolean
86
+ }
87
  }
88
  Enums: {
89
  [_ in never]: never
src/lib/words-food.ts ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const englishFoodWords = [
2
+ "PIZZA",
3
+ "PASTA",
4
+ "BREAD",
5
+ "CHEESE",
6
+ "APPLE",
7
+ "BANANA",
8
+ "ORANGE",
9
+ "CARROT",
10
+ "POTATO",
11
+ "TOMATO",
12
+ "CHICKEN",
13
+ "BEEF",
14
+ "FISH",
15
+ "RICE",
16
+ "SOUP",
17
+ "SALAD",
18
+ "CAKE",
19
+ "COOKIE",
20
+ "CHOCOLATE",
21
+ "HONEY",
22
+ "GRAPES",
23
+ "LEMON",
24
+ "PEPPER",
25
+ "ONION",
26
+ "GARLIC",
27
+ "CABBAGE",
28
+ "BROCCOLI",
29
+ "SPINACH",
30
+ "MUSHROOM",
31
+ "PUMPKIN",
32
+ "ZUCCHINI",
33
+ "BELL PEPPER",
34
+ "CORN",
35
+ "AVOCADO",
36
+ "YOGURT",
37
+ "NUTS",
38
+ "CEREAL",
39
+ "PUDDING",
40
+ "JAM"
41
+ ];
42
+
43
+ export const germanFoodWords = [
44
+ "PIZZA",
45
+ "NUDELN",
46
+ "BROT",
47
+ "KÄSE",
48
+ "APFEL",
49
+ "BANANE",
50
+ "ORANGE",
51
+ "KAROTTE",
52
+ "KARTOFFEL",
53
+ "TOMATE",
54
+ "HUHN",
55
+ "RINDFLEISCH",
56
+ "FISCH",
57
+ "REIS",
58
+ "SUPPE",
59
+ "SALAT",
60
+ "KUCHEN",
61
+ "KEKS",
62
+ "SCHOKOLADE",
63
+ "HONIG",
64
+ "TRAUBE",
65
+ "ZITRONE",
66
+ "PFEFFER",
67
+ "ZWIEBEL",
68
+ "KNOBLAUCH",
69
+ "KOHLSALAT",
70
+ "BROKKOLI",
71
+ "SPINAT",
72
+ "PILZ",
73
+ "KÜRBIS",
74
+ "ZUCCHINI",
75
+ "BELL-PAPRIKA",
76
+ "MAIS",
77
+ "AVOCADO",
78
+ "JOGHURT",
79
+ "NÜSSE",
80
+ "MÜSLI",
81
+ "PUDDING",
82
+ "MARMELADE"
83
+ ];
84
+
85
+ export const frenchFoodWords = [
86
+ "PIZZA",
87
+ "PÂTES",
88
+ "PAIN",
89
+ "FROMAGE",
90
+ "POMME",
91
+ "BANANE",
92
+ "ORANGE",
93
+ "CAROTTE",
94
+ "POMMETERRE",
95
+ "TOMATE",
96
+ "POULET",
97
+ "BOEUF",
98
+ "POISSON",
99
+ "RIZ",
100
+ "SOUPE",
101
+ "SALADE",
102
+ "GÂTEAU",
103
+ "BISCUIT",
104
+ "CHOCOLAT",
105
+ "MIEL",
106
+ "RAISIN",
107
+ "CITRON",
108
+ "POIVRON",
109
+ "OIGNON",
110
+ "AIL",
111
+ "CHOU",
112
+ "BROCOLI",
113
+ "ÉPINARD",
114
+ "CHAMPIGNON",
115
+ "COURGE",
116
+ "COURGETTE",
117
+ "POIVRON DOUX",
118
+ "MAÏS",
119
+ "AVOCAT",
120
+ "YAOURT",
121
+ "NOIX",
122
+ "CÉRÉALES",
123
+ "CRÈME",
124
+ "CONFITURE"
125
+ ];
126
+
127
+ export const italianFoodWords = [
128
+ "PIZZA",
129
+ "PASTA",
130
+ "PANE",
131
+ "FORMAGGIO",
132
+ "MELA",
133
+ "BANANA",
134
+ "ARANCIA",
135
+ "CAROTA",
136
+ "PATATA",
137
+ "POMODORO",
138
+ "POLLO",
139
+ "MANZO",
140
+ "PESCE",
141
+ "RISO",
142
+ "ZUPPA",
143
+ "INSALATA",
144
+ "TORTA",
145
+ "BISCOTTO",
146
+ "CIOCCOLATO",
147
+ "MIELE",
148
+ "UVA",
149
+ "LIMONE",
150
+ "PEPERONE",
151
+ "CIPOLLA",
152
+ "AGLIO",
153
+ "CAVOLO",
154
+ "BROCCOLI",
155
+ "SPINACI",
156
+ "FUNGHI",
157
+ "ZUCCA",
158
+ "ZUCCHINI",
159
+ "PEPERONE DOLCE",
160
+ "MAIS",
161
+ "AVOCADO",
162
+ "YOGURT",
163
+ "NOCI",
164
+ "CEREALI",
165
+ "CREMA",
166
+ "MARMELLATA"
167
+ ];
168
+
169
+ export const spanishFoodWords = [
170
+ "PIZZA",
171
+ "PASTA",
172
+ "PAN",
173
+ "QUESO",
174
+ "MANZANA",
175
+ "PLÁTANO",
176
+ "NARANJA",
177
+ "ZANAHORIA",
178
+ "PATATA",
179
+ "TOMATE",
180
+ "POLLO",
181
+ "TERNERA",
182
+ "PESCADO",
183
+ "ARROZ",
184
+ "SOPA",
185
+ "ENSALADA",
186
+ "PASTEL",
187
+ "GALLETA",
188
+ "CHOCOLATE",
189
+ "MIEL",
190
+ "UVAS",
191
+ "LIMÓN",
192
+ "PIMIENTA",
193
+ "CEBOLLA",
194
+ "AJO",
195
+ "COL",
196
+ "BRÓCOLI",
197
+ "ESPINACA",
198
+ "CHAMPIÑÓN",
199
+ "CALABAZA",
200
+ "CALABACÍN",
201
+ "PIMIENTO",
202
+ "MAÍZ",
203
+ "AGUACATE",
204
+ "YOGUR",
205
+ "NUECES",
206
+ "CEREALES",
207
+ "CREMA",
208
+ "MERMELADA"
209
+ ];
210
+
211
+ export const getRandomFoodWord = (language: string = 'en') => {
212
+ let wordList;
213
+ switch (language) {
214
+ case 'de':
215
+ wordList = germanFoodWords;
216
+ break;
217
+ case 'fr':
218
+ wordList = frenchFoodWords;
219
+ break;
220
+ case 'it':
221
+ wordList = italianFoodWords;
222
+ break;
223
+ case 'es':
224
+ wordList = spanishFoodWords;
225
+ break;
226
+ default:
227
+ wordList = englishFoodWords;
228
+ }
229
+ return wordList[Math.floor(Math.random() * wordList.length)];
230
+ };
src/lib/words-sports.ts ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const englishSportsWords = [
2
+ "SOCCER",
3
+ "TENNIS",
4
+ "GOLF",
5
+ "BASEBALL",
6
+ "BASKETBALL",
7
+ "VOLLEYBALL",
8
+ "HOCKEY",
9
+ "RUGBY",
10
+ "CRICKET",
11
+ "SWIMMING",
12
+ "RUNNING",
13
+ "CYCLING",
14
+ "SKIING",
15
+ "BOXING",
16
+ "SKATING",
17
+ "SURFING",
18
+ "CLIMBING",
19
+ "DIVING",
20
+ "SAILING",
21
+ "WRESTLING",
22
+ "LACROSSE",
23
+ "BADMINTON",
24
+ "FENCING",
25
+ "ARCHERY",
26
+ "MMA",
27
+ "KARATE",
28
+ "JUDO",
29
+ "PADDLEBOARDING",
30
+ "SNOWBOARDING",
31
+ "BILLIARDS",
32
+ "DARTS",
33
+ "GYMNASTICS",
34
+ "PILATES",
35
+ "YOGA",
36
+ "CROSSFIT",
37
+ "TRIATHLON",
38
+ "MARATHON",
39
+ "ULTRAMARATHON",
40
+ "POLO",
41
+ "SQUASH",
42
+ "BOWLING",
43
+ "CURLING",
44
+ "SKATEBOARDING",
45
+ "FLOORBALL",
46
+ "KITESURFING",
47
+ "BMX",
48
+ "TRAMPOLINE",
49
+ "SOFTBALL",
50
+ "RINGETTE",
51
+ "BANDY",
52
+ "SNOWSHOEING",
53
+ "PARAGLIDING",
54
+ "CANYONING",
55
+ "CAVING"
56
+ ];
57
+
58
+ export const germanSportsWords = [
59
+ "FUSSBALL",
60
+ "TENNIS",
61
+ "GOLF",
62
+ "BASEBALL",
63
+ "BASKETBALL",
64
+ "VOLLEYBALL",
65
+ "HOCKEY",
66
+ "RUGBY",
67
+ "CRICKET",
68
+ "SCHWIMMEN",
69
+ "LAUFEN",
70
+ "RADFAHREN",
71
+ "SKIFAHREN",
72
+ "BOXEN",
73
+ "EISLAUFEN",
74
+ "SURFEN",
75
+ "KLETTERN",
76
+ "TAUCHEN",
77
+ "SEGELN",
78
+ "RINGEN",
79
+ "TURNEN",
80
+ "BOGENSCHIESSEN",
81
+ "FECHTEN",
82
+ "MOTORSPORT",
83
+ "SCHIESSEN",
84
+ "REITEN",
85
+ "HANDBALL",
86
+ "SNOWBOARDEN",
87
+ "WASSERBALL",
88
+ "TRIATHLON",
89
+ "GEWICHTHEBEN",
90
+ "JUDO",
91
+ "TAEKWONDO",
92
+ "SKATEBOARDEN",
93
+ "WINDSURFEN",
94
+ "BADMINTON",
95
+ "TISCHTENNIS",
96
+ "SYNCHRONSCHWIMMEN",
97
+ "RUDERN",
98
+ "LACROSSE"
99
+ ];
100
+
101
+ export const frenchSportsWords = [
102
+ "FOOTBALL",
103
+ "TENNIS",
104
+ "GOLF",
105
+ "BASEBALL",
106
+ "BASKETBALL",
107
+ "VOLLEYBALL",
108
+ "HOCKEY",
109
+ "RUGBY",
110
+ "CRICKET",
111
+ "NATATION",
112
+ "COURSE",
113
+ "CYCLISME",
114
+ "SKI",
115
+ "BOXE",
116
+ "PATINAGE",
117
+ "SURF",
118
+ "ESCALADE",
119
+ "PLONGÉE",
120
+ "VOILE",
121
+ "LUTTE",
122
+ "GYMNASTIQUE",
123
+ "TIR À L'ARC",
124
+ "ESCRIME",
125
+ "MOTOCYCLISME",
126
+ "TIR",
127
+ "ÉQUITATION",
128
+ "HANDBALL",
129
+ "SNOWBOARD",
130
+ "WATER-POLO",
131
+ "TRIATHLON",
132
+ "HALTÉROPHILIE",
133
+ "JUDO",
134
+ "TAEKWONDO",
135
+ "SKATEBOARD",
136
+ "WINDSURF",
137
+ "BADMINTON",
138
+ "TENNIS DE TABLE",
139
+ "NATATION SYNCHRONISÉE",
140
+ "AVIRON",
141
+ "LACROSSE"
142
+ ];
143
+
144
+ export const italianSportsWords = [
145
+ "CALCIO",
146
+ "TENNIS",
147
+ "GOLF",
148
+ "BASEBALL",
149
+ "PALLACANESTRO",
150
+ "PALLAVOLO",
151
+ "HOCKEY",
152
+ "RUGBY",
153
+ "CRICKET",
154
+ "NUOTO",
155
+ "CORSA",
156
+ "CICLISMO",
157
+ "SCI",
158
+ "PUGILATO",
159
+ "PATTINAGGIO",
160
+ "SURF",
161
+ "ARRAMPICATA",
162
+ "IMMERSIONE",
163
+ "VELA",
164
+ "LOTTA",
165
+ "GINNASTICA",
166
+ "TIRO CON L'ARCO",
167
+ "SCHERMA",
168
+ "MOTOCICLISMO",
169
+ "TIRO A SEGNO",
170
+ "EQUITAZIONE",
171
+ "PALLAMANO",
172
+ "SNOWBOARD",
173
+ "PALLANUOTO",
174
+ "TRIATHLON",
175
+ "SOLLEVAMENTO PESI",
176
+ "JUDO",
177
+ "TAEKWONDO",
178
+ "SKATEBOARD",
179
+ "WINDSURF",
180
+ "BADMINTON",
181
+ "PING-PONG",
182
+ "NUOTO SINCRONIZZATO",
183
+ "CANOTTAGGIO",
184
+ "LACROSSE"
185
+ ];
186
+
187
+ export const spanishSportsWords = [
188
+ "FÚTBOL",
189
+ "TENIS",
190
+ "GOLF",
191
+ "BÉISBOL",
192
+ "BALONCESTO",
193
+ "VOLEIBOL",
194
+ "HOCKEY",
195
+ "RUGBY",
196
+ "CRÍQUET",
197
+ "NATACIÓN",
198
+ "CARRERA",
199
+ "CICLISMO",
200
+ "ESQUÍ",
201
+ "BOXEO",
202
+ "PATINAJE",
203
+ "SURF",
204
+ "ESCALADA",
205
+ "BUCEO",
206
+ "VELA",
207
+ "LUCHA",
208
+ "GIMNASIA",
209
+ "TIRO CON ARCO",
210
+ "ESGRIMA",
211
+ "MOTOCICLISMO",
212
+ "TIRO",
213
+ "EQUITACIÓN",
214
+ "BALONMANO",
215
+ "SNOWBOARD",
216
+ "WATERPOLO",
217
+ "TRIATLÓN",
218
+ "HALTEROFILIA",
219
+ "JUDO",
220
+ "TAEKWONDO",
221
+ "SKATEBOARD",
222
+ "WINDSURF",
223
+ "BÁDMINTON",
224
+ "TENIS DE MESA",
225
+ "NATACIÓN SINCRONIZADA",
226
+ "REMO",
227
+ "LACROSSE"
228
+ ];
229
+
230
+
231
+ export const getRandomSportsWord = (language: string = 'en') => {
232
+ let wordList;
233
+ switch (language) {
234
+ case 'de':
235
+ wordList = germanSportsWords;
236
+ break;
237
+ case 'fr':
238
+ wordList = frenchSportsWords;
239
+ break;
240
+ case 'it':
241
+ wordList = italianSportsWords;
242
+ break;
243
+ case 'es':
244
+ wordList = spanishSportsWords;
245
+ break;
246
+ default:
247
+ wordList = englishSportsWords;
248
+ }
249
+ return wordList[Math.floor(Math.random() * wordList.length)];
250
+ };
src/lib/words-standard.ts ADDED
@@ -0,0 +1,1160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const englishWords = [
2
+ "DOG",
3
+ "CAT",
4
+ "SUN",
5
+ "RAIN",
6
+ "TREE",
7
+ "STAR",
8
+ "MOON",
9
+ "FISH",
10
+ "BIRD",
11
+ "CLOUD",
12
+ "SKY",
13
+ "WIND",
14
+ "SNOW",
15
+ "FLOWER",
16
+ "BUTTERFLY",
17
+ "WATER",
18
+ "OCEAN",
19
+ "RIVER",
20
+ "MOUNTAIN",
21
+ "FOREST",
22
+ "HOUSE",
23
+ "CANDLE",
24
+ "GARDEN",
25
+ "BRIDGE",
26
+ "ISLAND",
27
+ "BREEZE",
28
+ "LIGHT",
29
+ "THUNDER",
30
+ "RAINBOW",
31
+ "SMILE",
32
+ "FRIEND",
33
+ "FAMILY",
34
+ "APPLE",
35
+ "BANANA",
36
+ "CAR",
37
+ "BOAT",
38
+ "BALL",
39
+ "CAKE",
40
+ "FROG",
41
+ "HORSE",
42
+ "LION",
43
+ "MONKEY",
44
+ "PANDA",
45
+ "PLANE",
46
+ "TRAIN",
47
+ "CANDY",
48
+ "KITE",
49
+ "BALLOON",
50
+ "PARK",
51
+ "BEACH",
52
+ "TOY",
53
+ "BOOK",
54
+ "BUBBLE",
55
+ "SHELL",
56
+ "PEN",
57
+ "ICE",
58
+ "HAT",
59
+ "SHOE",
60
+ "CLOCK",
61
+ "BED",
62
+ "CUP",
63
+ "KEY",
64
+ "DOOR",
65
+ "CHICKEN",
66
+ "DUCK",
67
+ "SHEEP",
68
+ "COW",
69
+ "PIG",
70
+ "GOAT",
71
+ "FOX",
72
+ "BEAR",
73
+ "DEER",
74
+ "OWL",
75
+ "EGG",
76
+ "NEST",
77
+ "ROCK",
78
+ "LEAF",
79
+ "BRUSH",
80
+ "TOOTH",
81
+ "HAND",
82
+ "FEET",
83
+ "EYE",
84
+ "NOSE",
85
+ "EAR",
86
+ "MOUTH",
87
+ "CHILD",
88
+ "RAINCOAT",
89
+ "LADDER",
90
+ "WINDOW",
91
+ "DOCTOR",
92
+ "NURSE",
93
+ "TEACHER",
94
+ "STUDENT",
95
+ "PENCIL",
96
+ "TABLE",
97
+ "CHAIR",
98
+ "LAMP",
99
+ "MIRROR",
100
+ "BOWL",
101
+ "PLATE",
102
+ "SPOON",
103
+ "FORK",
104
+ "KNIFE",
105
+ "GLASS",
106
+ "STRAW",
107
+ "RULER",
108
+ "PAPER",
109
+ "BASKET",
110
+ "CARPET",
111
+ "SOFA",
112
+ "TELEVISION",
113
+ "RADIO",
114
+ "BATTERY",
115
+ "CANDLE",
116
+ "FENCE",
117
+ "MAILBOX",
118
+ "BRICK",
119
+ "LANTERN",
120
+ "WHEEL",
121
+ "BELL",
122
+ "UMBRELLA",
123
+ "TRUCK",
124
+ "MOTORCYCLE",
125
+ "BICYCLE",
126
+ "STOVE",
127
+ "REFRIGERATOR",
128
+ "MICROWAVE",
129
+ "WASHER",
130
+ "DRYER",
131
+ "FURNACE",
132
+ "FAN",
133
+ "PAINTBRUSH",
134
+ "BUCKET",
135
+ "SPONGE",
136
+ "SOAP",
137
+ "TOWEL",
138
+ "CLOTH",
139
+ "SCISSORS",
140
+ "TAPE",
141
+ "RIBBON",
142
+ "THREAD",
143
+ "NEEDLE",
144
+ "BUTTON",
145
+ "ZIPPER",
146
+ "SLIPPER",
147
+ "COAT",
148
+ "MITTEN",
149
+ "SCARF",
150
+ "GLOVE",
151
+ "PANTS",
152
+ "SHIRT",
153
+ "JACKET",
154
+ "DRESS",
155
+ "SKIRT",
156
+ "SOCK",
157
+ "BOOT",
158
+ "SANDAL",
159
+ "HAT",
160
+ "CAP",
161
+ "MASK",
162
+ "SUNGALASSES",
163
+ "WATCH",
164
+ "NECKLACE",
165
+ "BRACELET",
166
+ "RING",
167
+ "EARRING",
168
+ "BACKPACK",
169
+ "SUITCASE",
170
+ "TICKET",
171
+ "PASSPORT",
172
+ "MAP",
173
+ "COMPASS",
174
+ "TORCH",
175
+ "FLASHLIGHT",
176
+ "CAMPFIRE",
177
+ "TENT",
178
+ "SLEEPINGBAG",
179
+ "PICNIC",
180
+ "BENCH",
181
+ "FENCE",
182
+ "GATE",
183
+ "SIGN",
184
+ "CROSSWALK",
185
+ "TRAFFICLIGHT",
186
+ "SIDEWALK",
187
+ "LANTERN",
188
+ "BALLOON",
189
+ "POSTCARD",
190
+ "STAMP",
191
+ "LETTER",
192
+ "ENVELOPE",
193
+ "PARKING",
194
+ "STREET",
195
+ "HIGHWAY",
196
+ "BRIDGE",
197
+ "TUNNEL",
198
+ "STATUE",
199
+ "FOUNTAIN",
200
+ "TOWER",
201
+ "CASTLE",
202
+ "PYRAMID",
203
+ "PLANET",
204
+ "GALAXY",
205
+ "SATELLITE",
206
+ "ASTRONAUT",
207
+ "TELESCOPE",
208
+ "MICROSCOPE",
209
+ "MAGNET",
210
+ "BATTERY",
211
+ "BULB",
212
+ "SOCKET",
213
+ "PLUG",
214
+ "WIRE",
215
+ "SWITCH",
216
+ "CIRCUIT",
217
+ "ROBOT",
218
+ "COMPUTER",
219
+ "MOUSE",
220
+ "KEYBOARD",
221
+ "SCREEN",
222
+ "PRINTER",
223
+ "SPEAKER",
224
+ "HEADPHONE",
225
+ "PHONE",
226
+ "CAMERA"
227
+ ];
228
+
229
+ export const germanWords = [
230
+ "HUND",
231
+ "KATZE",
232
+ "SONNE",
233
+ "REGEN",
234
+ "BAUM",
235
+ "STERN",
236
+ "MOND",
237
+ "FISCH",
238
+ "VOGEL",
239
+ "WOLKE",
240
+ "HIMMEL",
241
+ "WIND",
242
+ "SCHNEE",
243
+ "BLUME",
244
+ "SCHMETTERLING",
245
+ "WASSER",
246
+ "OZEAN",
247
+ "FLUSS",
248
+ "BERG",
249
+ "WALD",
250
+ "HAUS",
251
+ "KERZE",
252
+ "GARTEN",
253
+ "BRÜCKE",
254
+ "INSEL",
255
+ "BRISE",
256
+ "LICHT",
257
+ "DONNER",
258
+ "REGENBOGEN",
259
+ "LÄCHELN",
260
+ "FREUND",
261
+ "FAMILIE",
262
+ "APFEL",
263
+ "BANANE",
264
+ "AUTO",
265
+ "BOOT",
266
+ "BALL",
267
+ "KUCHEN",
268
+ "FROSCH",
269
+ "PFERD",
270
+ "LÖWE",
271
+ "AFFE",
272
+ "PANDA",
273
+ "FLUGZEUG",
274
+ "ZUG",
275
+ "SÜSSIGKEIT",
276
+ "DRACHEN",
277
+ "BALLON",
278
+ "PARK",
279
+ "STRAND",
280
+ "SPIELZEUG",
281
+ "BUCH",
282
+ "BLASE",
283
+ "MUSCHEL",
284
+ "STIFT",
285
+ "EIS",
286
+ "HUT",
287
+ "SCHUH",
288
+ "UHR",
289
+ "BETT",
290
+ "TASSE",
291
+ "SCHLÜSSEL",
292
+ "TÜR",
293
+ "HÜHNCHEN",
294
+ "ENTE",
295
+ "SCHAF",
296
+ "KUH",
297
+ "SCHWEIN",
298
+ "ZIEGE",
299
+ "FUCHS",
300
+ "BÄR",
301
+ "REH",
302
+ "EULE",
303
+ "EI",
304
+ "NEST",
305
+ "STEIN",
306
+ "BLATT",
307
+ "PINSEL",
308
+ "ZAHN",
309
+ "HAND",
310
+ "FÜSSE",
311
+ "AUGE",
312
+ "NASE",
313
+ "OHR",
314
+ "MUND",
315
+ "KIND",
316
+ "REGENMANTEL",
317
+ "LEITER",
318
+ "FENSTER",
319
+ "ARZT",
320
+ "KRANKENSCHWESTER",
321
+ "LEHRER",
322
+ "STUDENT",
323
+ "BLEISTIFT",
324
+ "TISCH",
325
+ "STUHL",
326
+ "LAMPE",
327
+ "SPIEGEL",
328
+ "SCHÜSSEL",
329
+ "TELLER",
330
+ "LÖFFEL",
331
+ "GABEL",
332
+ "MESSER",
333
+ "GLAS",
334
+ "STROHHALM",
335
+ "LINEAL",
336
+ "PAPIER",
337
+ "KORB",
338
+ "TEPPICH",
339
+ "SOFA",
340
+ "FERNSEHER",
341
+ "RADIO",
342
+ "BATTERIE",
343
+ "KERZE", // duplicate
344
+ "ZAUN",
345
+ "BRIEFKASTEN",
346
+ "BACKSTEIN",
347
+ "LATERNE",
348
+ "RAD",
349
+ "GLOCKE",
350
+ "REGENSCHIRM",
351
+ "LASTWAGEN",
352
+ "MOTORRAD",
353
+ "FAHRRAD",
354
+ "HERD",
355
+ "KÜHLSCHRANK",
356
+ "MIKROWELLE",
357
+ "WASCHMASCHINE",
358
+ "TROCKNER",
359
+ "OFEN",
360
+ "VENTILATOR",
361
+ "PINSEL", // paintbrush (same as index 78 but used for “brush” too)
362
+ "EIMER",
363
+ "SCHWAMM",
364
+ "SEIFE",
365
+ "HANDTUCH",
366
+ "STOFF",
367
+ "SCHERE",
368
+ "KLEBEBAND",
369
+ "BAND",
370
+ "FADEN",
371
+ "NADEL",
372
+ "KNOPF",
373
+ "REISSVERSCHLUSS",
374
+ "HAUSSCHUH",
375
+ "MANTEL",
376
+ "FAUSTHANDSCHUH",
377
+ "SCHAL",
378
+ "HANDSCHUH",
379
+ "HOSE",
380
+ "HEMD",
381
+ "JACKE",
382
+ "KLEID",
383
+ "ROCK",
384
+ "SOCKE",
385
+ "STIEFEL",
386
+ "SANDALE",
387
+ "HUT", // duplicate
388
+ "MÜTZE",
389
+ "MASKE",
390
+ "SONNENBRILLE",
391
+ "UHR",
392
+ "HALSKETTE",
393
+ "ARMBAND",
394
+ "RING",
395
+ "OHRRING",
396
+ "RUCKSACK",
397
+ "KOFFER",
398
+ "TICKET",
399
+ "REISEPASS",
400
+ "KARTE",
401
+ "KOMPASS",
402
+ "FACKEL",
403
+ "TASCHENLAMPE",
404
+ "LAGERFEUER",
405
+ "ZELT",
406
+ "SCHLAFSACK",
407
+ "PICKNICK",
408
+ "BANK",
409
+ "ZAUN", // duplicate
410
+ "TOR",
411
+ "SCHILD",
412
+ "ZEBRASTREIFEN",
413
+ "VERKEHRSAMPEL",
414
+ "BÜRGERSTEIG",
415
+ "LATERNE", // duplicate
416
+ "BALLON", // duplicate
417
+ "POSTKARTE",
418
+ "BRIEFMARKE",
419
+ "BRIEF",
420
+ "UMSCHLAG",
421
+ "PARKPLATZ",
422
+ "STRAßE",
423
+ "AUTOBAHN",
424
+ "BRÜCKE", // duplicate
425
+ "TUNNEL",
426
+ "STATUE",
427
+ "BRUNNEN",
428
+ "TURM",
429
+ "SCHLOSS",
430
+ "PYRAMIDE",
431
+ "PLANET",
432
+ "GALAXIE",
433
+ "SATELLIT",
434
+ "ASTRONAUT",
435
+ "TELESKOP",
436
+ "MIKROSKOP",
437
+ "MAGNET",
438
+ "BATTERIE", // duplicate
439
+ "GLÜHBIRNE",
440
+ "STECKDOSE",
441
+ "STECKER",
442
+ "DRAHT",
443
+ "SCHALTER",
444
+ "SCHALTUNG",
445
+ "ROBOTER",
446
+ "COMPUTER",
447
+ "MAUS",
448
+ "TASTATUR",
449
+ "BILDSCHIRM",
450
+ "DRUCKER",
451
+ "LAUTSPRECHER",
452
+ "KOPFHÖRER",
453
+ "TELEFON",
454
+ "KAMERA"
455
+ ];
456
+
457
+ export const frenchWords = [
458
+ "CHIEN",
459
+ "CHAT",
460
+ "SOLEIL",
461
+ "PLUIE",
462
+ "ARBRE",
463
+ "ÉTOILE",
464
+ "LUNE",
465
+ "POISSON",
466
+ "OISEAU",
467
+ "NUAGE",
468
+ "CIEL",
469
+ "VENT",
470
+ "NEIGE",
471
+ "FLEUR",
472
+ "PAPILLON",
473
+ "EAU",
474
+ "OCÉAN",
475
+ "FLEUVE",
476
+ "MONTAGNE",
477
+ "FORÊT",
478
+ "MAISON",
479
+ "BOUGIE",
480
+ "JARDIN",
481
+ "PONT",
482
+ "ÎLE",
483
+ "BRISE",
484
+ "LUMIÈRE",
485
+ "TONNERRE",
486
+ "ARC-EN-CIEL",
487
+ "SOURIRE",
488
+ "AMI",
489
+ "FAMILLE",
490
+ "POMME",
491
+ "BANANE",
492
+ "VOITURE",
493
+ "BATEAU",
494
+ "BALLE",
495
+ "GÂTEAU",
496
+ "GRENOUILLE",
497
+ "CHEVAL",
498
+ "LION",
499
+ "SINGE",
500
+ "PANDA",
501
+ "AVION",
502
+ "TRAIN",
503
+ "BONBON",
504
+ "CERF-VOLANT",
505
+ "BALLON",
506
+ "PARC",
507
+ "PLAGE",
508
+ "JOUET",
509
+ "LIVRE",
510
+ "BULLE",
511
+ "COQUILLAGE",
512
+ "STYLO",
513
+ "GLACE",
514
+ "CHAPEAU",
515
+ "CHAUSSURE",
516
+ "HORLOGE",
517
+ "LIT",
518
+ "TASSE",
519
+ "CLÉ",
520
+ "PORTE",
521
+ "POULET",
522
+ "CANARD",
523
+ "MOUTON",
524
+ "VACHE",
525
+ "COCHON",
526
+ "CHÈVRE",
527
+ "RENARD",
528
+ "OURS",
529
+ "CERF",
530
+ "HIBOU",
531
+ "ŒUF",
532
+ "NID",
533
+ "ROCHE",
534
+ "FEUILLE",
535
+ "PINCEAU",
536
+ "DENT",
537
+ "MAIN",
538
+ "PIEDS",
539
+ "ŒIL",
540
+ "NEZ",
541
+ "OREILLE",
542
+ "BOUCHE",
543
+ "ENFANT",
544
+ "IMPERMÉABLE",
545
+ "ÉCHELLE",
546
+ "FENÊTRE",
547
+ "MÉDECIN",
548
+ "INFIRMIÈRE",
549
+ "ENSEIGNANT",
550
+ "ÉTUDIANT",
551
+ "CRAYON",
552
+ "TABLE",
553
+ "CHAISE",
554
+ "LAMPE",
555
+ "MIROIR",
556
+ "BOL",
557
+ "ASSIETTE",
558
+ "CUILLÈRE",
559
+ "FOURCHETTE",
560
+ "COUTEAU",
561
+ "VERRE",
562
+ "PAILLE",
563
+ "RÈGLE",
564
+ "PAPIER",
565
+ "PANIER",
566
+ "TAPIS",
567
+ "CANAPÉ",
568
+ "TÉLÉVISION",
569
+ "RADIO",
570
+ "PILE",
571
+ "BOUGIE", // duplicate
572
+ "CLÔTURE",
573
+ "BOÎTE AUX LETTRES",
574
+ "BRIQUE",
575
+ "LANTERNE",
576
+ "ROUE",
577
+ "CLOCHE",
578
+ "PARAPLUIE",
579
+ "CAMION",
580
+ "MOTO",
581
+ "VÉLO",
582
+ "CUISINIÈRE",
583
+ "RÉFRIGÉRATEUR",
584
+ "MICRO-ONDES",
585
+ "LAVE-LINGE",
586
+ "SÈCHE-LINGE",
587
+ "FOURNAISE",
588
+ "VENTILATEUR",
589
+ "PINCEAU", // paintbrush
590
+ "SEAU",
591
+ "ÉPONGE",
592
+ "SAVON",
593
+ "SERVIETTE",
594
+ "TISSU",
595
+ "CISEAUX",
596
+ "SCOTCH",
597
+ "RUBAN",
598
+ "FIL",
599
+ "AIGUILLE",
600
+ "BOUTON",
601
+ "FERMETURE ÉCLAIR",
602
+ "PANTOUFLE",
603
+ "MANTEAU",
604
+ "MOUFLE",
605
+ "ÉCHARPE",
606
+ "GANT",
607
+ "PANTALON",
608
+ "CHEMISE",
609
+ "VESTE",
610
+ "ROBE",
611
+ "JUPE",
612
+ "CHAUSSETTE",
613
+ "BOTTE",
614
+ "SANDALE",
615
+ "CHAPEAU", // duplicate
616
+ "CASQUETTE",
617
+ "MASQUE",
618
+ "LUNETTES DE SOLEIL",
619
+ "MONTRE",
620
+ "COLLIER",
621
+ "BRACELET",
622
+ "BAGUE",
623
+ "BOUCLE D'OREILLE",
624
+ "SAC À DOS",
625
+ "VALISE",
626
+ "BILLET",
627
+ "PASSEPORT",
628
+ "CARTE",
629
+ "BOUSSOLE",
630
+ "TORCHE",
631
+ "LAMPE DE POCHE",
632
+ "FEU DE CAMP",
633
+ "TENTE",
634
+ "SAC DE COUCHAGE",
635
+ "PIQUE-NIQUE",
636
+ "BANC",
637
+ "CLÔTURE", // duplicate
638
+ "PORTAIL",
639
+ "PANNEAU",
640
+ "PASSAGE PIÉTON",
641
+ "FEU DE SIGNALISATION",
642
+ "TROTTOIR",
643
+ "LANTERNE", // duplicate
644
+ "BALLON", // duplicate
645
+ "CARTE POSTALE",
646
+ "TIMBRE",
647
+ "LETTRE",
648
+ "ENVELOPPE",
649
+ "PARKING",
650
+ "RUE",
651
+ "AUTOROUTE",
652
+ "PONT", // duplicate
653
+ "TUNNEL",
654
+ "STATUE",
655
+ "FONTAINE",
656
+ "TOUR",
657
+ "CHÂTEAU",
658
+ "PYRAMIDE",
659
+ "PLANÈTE",
660
+ "GALAXIE",
661
+ "SATELLITE",
662
+ "ASTRONAUTE",
663
+ "TÉLESCOPE",
664
+ "MICROSCOPE",
665
+ "AIMANT",
666
+ "PILE", // duplicate
667
+ "AMPOULE",
668
+ "PRISE",
669
+ "FICHE",
670
+ "FIL",
671
+ "INTERRUPTEUR",
672
+ "CIRCUIT",
673
+ "ROBOT",
674
+ "ORDINATEUR",
675
+ "SOURIS",
676
+ "CLAVIER",
677
+ "ÉCRAN",
678
+ "IMPRIMANTE",
679
+ "HAUT-PARLEUR",
680
+ "CASQUE",
681
+ "TÉLÉPHONE",
682
+ "APPAREIL PHOTO"
683
+ ];
684
+
685
+ export const italianWords = [
686
+ "CANE",
687
+ "GATTO",
688
+ "SOLE",
689
+ "PIOGGIA",
690
+ "ALBERO",
691
+ "STELLA",
692
+ "LUNA",
693
+ "PESCE",
694
+ "UCCELLO",
695
+ "NUVOLA",
696
+ "CIELO",
697
+ "VENTO",
698
+ "NEVE",
699
+ "FIORE",
700
+ "FARFALLA",
701
+ "ACQUA",
702
+ "OCEANO",
703
+ "FIUME",
704
+ "MONTAGNA",
705
+ "FORESTA",
706
+ "CASA",
707
+ "CANDELA",
708
+ "GIARDINO",
709
+ "PONTE",
710
+ "ISOLA",
711
+ "BREZZA",
712
+ "LUCE",
713
+ "TUONO",
714
+ "ARCOBALENO",
715
+ "SORRISO",
716
+ "AMICO",
717
+ "FAMIGLIA",
718
+ "MELA",
719
+ "BANANA",
720
+ "AUTO",
721
+ "BARCA",
722
+ "PALLA",
723
+ "TORTA",
724
+ "RANA",
725
+ "CAVALLO",
726
+ "LEONE",
727
+ "SCIMMIA",
728
+ "PANDA",
729
+ "AEREO",
730
+ "TRENO",
731
+ "CARAMELLA",
732
+ "AQUILONE",
733
+ "PALLONCINO",
734
+ "PARCO",
735
+ "SPIAGGIA",
736
+ "GIOCATTOLO",
737
+ "LIBRO",
738
+ "BOLLA",
739
+ "CONCHIGLIA",
740
+ "PENNA",
741
+ "GHIACCIO",
742
+ "CAPPELLO",
743
+ "SCARPA",
744
+ "OROLOGIO",
745
+ "LETTO",
746
+ "TAZZA",
747
+ "CHIAVE",
748
+ "PORTA",
749
+ "POLLO",
750
+ "ANATRA",
751
+ "PECORA",
752
+ "MUCCA",
753
+ "MAIALE",
754
+ "CAPRA",
755
+ "VOLPE",
756
+ "ORSO",
757
+ "CERVO",
758
+ "GUFO",
759
+ "UOVO",
760
+ "NIDO",
761
+ "ROCCIA",
762
+ "FOGLIA",
763
+ "PENNELLO",
764
+ "DENTE",
765
+ "MANO",
766
+ "PIEDI",
767
+ "OCCHIO",
768
+ "NASO",
769
+ "ORECCHIO",
770
+ "BOCCA",
771
+ "BAMBINO",
772
+ "IMPERMEABILE",
773
+ "SCALA",
774
+ "FINESTRA",
775
+ "MEDICO",
776
+ "INFERMIERA",
777
+ "INSEGNANTE",
778
+ "STUDENTE",
779
+ "MATITA",
780
+ "TAVOLO",
781
+ "SEDIA",
782
+ "LAMPADA",
783
+ "SPECCHIO",
784
+ "CIOTOLA",
785
+ "PIATTO",
786
+ "CUCCHIAIO",
787
+ "FORCHETTA",
788
+ "COLTELLO",
789
+ "BICCHIERE",
790
+ "CANNUCCIA",
791
+ "RIGHELLO",
792
+ "CARTA",
793
+ "CESTINO",
794
+ "TAPPETO",
795
+ "DIVANO",
796
+ "TELEVISIONE",
797
+ "RADIO",
798
+ "BATTERIA",
799
+ "CANDELA", // duplicate
800
+ "RECINTO",
801
+ "CASSETTA DELLE LETTERE",
802
+ "MATTONE",
803
+ "LANTERNA",
804
+ "RUOTA",
805
+ "CAMPANA",
806
+ "OMBRELLO",
807
+ "CAMION",
808
+ "MOTOCICLETTA",
809
+ "BICICLETTA",
810
+ "FORNELLO",
811
+ "FRIGORIFERO",
812
+ "MICROONDE",
813
+ "LAVATRICE",
814
+ "ASCIUGATRICE",
815
+ "FORNO",
816
+ "VENTILATORE",
817
+ "PENNELLO", // paintbrush
818
+ "SECCHIO",
819
+ "SPUGNA",
820
+ "SAPONE",
821
+ "ASCIUGAMANO",
822
+ "PANNO",
823
+ "FORBICI",
824
+ "NASTRO ADESIVO",
825
+ "NASTRO",
826
+ "FILO",
827
+ "AGO",
828
+ "BOTTONE",
829
+ "CERNIERA",
830
+ "PANTOFOLA",
831
+ "CAPPOTTO",
832
+ "MOFFOLA",
833
+ "SCIARPA",
834
+ "GUANTO",
835
+ "PANTALONI",
836
+ "CAMICIA",
837
+ "GIACCA",
838
+ "VESTITO",
839
+ "GONNA",
840
+ "CALZINO",
841
+ "STIVALE", // corrected translation for BOOT
842
+ "SANDALO",
843
+ "CAPPELLO", // duplicate
844
+ "BERRETTO", // instead of MÜTZE, let's keep it consistent in Italian
845
+ "MASCHERA",
846
+ "OCCHIALI DA SOLE",
847
+ "OROLOGIO",
848
+ "COLLANA",
849
+ "BRACCIALETTO",
850
+ "ANELLO",
851
+ "ORECCHINO",
852
+ "ZAINO",
853
+ "VALIGIA",
854
+ "BIGLIETTO",
855
+ "PASSAPORTO",
856
+ "CARTINA",
857
+ "BUSSOLA",
858
+ "TORCIA",
859
+ "TORCIA ELETTRICA",
860
+ "FALÒ",
861
+ "TENDA",
862
+ "SACCO A PELO",
863
+ "PICNIC",
864
+ "PANCHINA",
865
+ "RECINTO", // duplicate
866
+ "CANCELLO",
867
+ "SEGNALE",
868
+ "STRISCE PEDONALI",
869
+ "SEMAFORO",
870
+ "MARCIAPIEDE",
871
+ "LANTERNA", // duplicate
872
+ "PALLONCINO", // duplicate
873
+ "CARTOLINA",
874
+ "FRANCOBOLLO",
875
+ "LETTERA",
876
+ "BUSTA",
877
+ "PARCHEGGIO",
878
+ "STRADA",
879
+ "AUTOSTRADA",
880
+ "PONTE", // duplicate
881
+ "TUNNEL",
882
+ "STATUA",
883
+ "FONTANA",
884
+ "TORRE",
885
+ "CASTELLO",
886
+ "PIRAMIDE",
887
+ "PIANETA",
888
+ "GALASSIA",
889
+ "SATELLITE",
890
+ "ASTRONAUTA",
891
+ "TELESCOPIO",
892
+ "MICROSCOPIO",
893
+ "MAGNETE",
894
+ "BATTERIA", // duplicate
895
+ "LAMPADINA",
896
+ "PRESA",
897
+ "SPINA",
898
+ "FILO",
899
+ "INTERRUTTORE",
900
+ "CIRCUITO",
901
+ "ROBOT",
902
+ "COMPUTER",
903
+ "MOUSE",
904
+ "TASTIERA",
905
+ "SCHERMO",
906
+ "STAMPANTE",
907
+ "ALTOPARLANTE",
908
+ "CUFFIE",
909
+ "TELEFONO",
910
+ "FOTOCAMERA"
911
+ ];
912
+
913
+ export const spanishWords = [
914
+ "PERRO",
915
+ "GATO",
916
+ "SOL",
917
+ "LLUVIA",
918
+ "ÁRBOL",
919
+ "ESTRELLA",
920
+ "LUNA",
921
+ "PEZ",
922
+ "PÁJARO",
923
+ "NUBE",
924
+ "CIELO",
925
+ "VIENTO",
926
+ "NIEVE",
927
+ "FLOR",
928
+ "MARIPOSA",
929
+ "AGUA",
930
+ "OCÉANO",
931
+ "RÍO",
932
+ "MONTAÑA",
933
+ "BOSQUE",
934
+ "CASA",
935
+ "VELA",
936
+ "JARDÍN",
937
+ "PUENTE",
938
+ "ISLA",
939
+ "BRISA",
940
+ "LUZ",
941
+ "TRUENO",
942
+ "ARCOÍRIS",
943
+ "SONRISA",
944
+ "AMIGO",
945
+ "FAMILIA",
946
+ "MANZANA",
947
+ "BANANA",
948
+ "COCHE",
949
+ "BARCO",
950
+ "PELOTA",
951
+ "PASTEL",
952
+ "RANA",
953
+ "CABALLO",
954
+ "LEÓN",
955
+ "MONO",
956
+ "OSO PANDA",
957
+ "AVIÓN",
958
+ "TREN",
959
+ "CARAMELO",
960
+ "COMETA",
961
+ "GLOBO",
962
+ "PARQUE",
963
+ "PLAYA",
964
+ "JUGUETE",
965
+ "LIBRO",
966
+ "BURBUJA",
967
+ "CONCHA",
968
+ "BOLÍGRAFO",
969
+ "HIELO",
970
+ "SOMBRERO",
971
+ "ZAPATO",
972
+ "RELOJ",
973
+ "CAMA",
974
+ "TAZA",
975
+ "LLAVE",
976
+ "PUERTA",
977
+ "POLLO",
978
+ "PATO",
979
+ "OVEJA",
980
+ "VACA",
981
+ "CERDO",
982
+ "CABRA",
983
+ "ZORRO",
984
+ "OSO",
985
+ "CIERVO",
986
+ "BÚHO",
987
+ "HUEVO",
988
+ "NIDO",
989
+ "ROCA",
990
+ "HOJA",
991
+ "PINCEL",
992
+ "DIENTE",
993
+ "MANO",
994
+ "PIES",
995
+ "OJO",
996
+ "NARIZ",
997
+ "OREJA",
998
+ "BOCA",
999
+ "NIÑO",
1000
+ "IMPERMEABLE",
1001
+ "ESCALERA",
1002
+ "VENTANA",
1003
+ "MÉDICO",
1004
+ "ENFERMERA",
1005
+ "MAESTRO",
1006
+ "ESTUDIANTE",
1007
+ "LÁPIZ",
1008
+ "MESA",
1009
+ "SILLA",
1010
+ "LÁMPARA",
1011
+ "ESPEJO",
1012
+ "CUENCO",
1013
+ "PLATO",
1014
+ "CUCHARA",
1015
+ "TENEDOR",
1016
+ "CUCHILLO",
1017
+ "VASO",
1018
+ "PAJITA",
1019
+ "REGLA",
1020
+ "PAPEL",
1021
+ "CESTA",
1022
+ "ALFOMBRA",
1023
+ "SOFÁ",
1024
+ "TELEVISIÓN",
1025
+ "RADIO",
1026
+ "BATERÍA",
1027
+ "VELA", // duplicate
1028
+ "VALLA",
1029
+ "BUZÓN",
1030
+ "LADRILLO",
1031
+ "FAROL",
1032
+ "RUEDA",
1033
+ "CAMPANA",
1034
+ "PARAGUAS",
1035
+ "CAMIÓN",
1036
+ "MOTOCICLETA",
1037
+ "BICICLETA",
1038
+ "ESTUFA",
1039
+ "REFRIGERADOR",
1040
+ "MICROONDAS",
1041
+ "LAVADORA",
1042
+ "SECADORA",
1043
+ "HORNO",
1044
+ "VENTILADOR",
1045
+ "PINCEL", // paintbrush
1046
+ "CUBO",
1047
+ "ESPONJA",
1048
+ "JABÓN",
1049
+ "TOALLA",
1050
+ "TELA",
1051
+ "TIJERAS",
1052
+ "CINTA",
1053
+ "CINTA", // RIBBON (could also say “LISTÓN”)
1054
+ "HILO",
1055
+ "AGUJA",
1056
+ "BOTÓN",
1057
+ "CREMALLERA",
1058
+ "PANTUFLA",
1059
+ "ABRIGO",
1060
+ "MANOPLA",
1061
+ "BUFANDA",
1062
+ "GUANTE",
1063
+ "PANTALONES",
1064
+ "CAMISA",
1065
+ "CHAQUETA",
1066
+ "VESTIDO",
1067
+ "FALDA",
1068
+ "CALCETÍN",
1069
+ "BOTA",
1070
+ "SANDALIA",
1071
+ "SOMBRERO", // duplicate
1072
+ "GORRA",
1073
+ "MÁSCARA",
1074
+ "GAFAS DE SOL",
1075
+ "RELOJ",
1076
+ "COLLAR",
1077
+ "PULSERA",
1078
+ "ANILLO",
1079
+ "PENDIENTE",
1080
+ "MOCHILA",
1081
+ "MALETA",
1082
+ "BILLETE",
1083
+ "PASAPORTE",
1084
+ "MAPA",
1085
+ "BRÚJULA",
1086
+ "ANTORCHA",
1087
+ "LINTERNA",
1088
+ "HOGUERA",
1089
+ "TIENDA DE CAMPAÑA",
1090
+ "SACO DE DORMIR",
1091
+ "PICNIC",
1092
+ "BANCO",
1093
+ "VALLA", // duplicate
1094
+ "PUERTA", // gate can be “PUERTA” or “PORTÓN”; used PUERTA earlier for “door,” so let's keep “PORTÓN” for gate below
1095
+ "SEÑAL",
1096
+ "PASO DE PEATONES",
1097
+ "SEMÁFORO",
1098
+ "ACERA",
1099
+ "FAROL", // duplicate
1100
+ "GLOBO", // duplicate
1101
+ "POSTAL",
1102
+ "SELLO",
1103
+ "CARTA",
1104
+ "SOBRE",
1105
+ "ESTACIONAMIENTO",
1106
+ "CALLE",
1107
+ "AUTOPISTA",
1108
+ "PUENTE", // duplicate
1109
+ "TÚNEL",
1110
+ "ESTATUA",
1111
+ "FUENTE",
1112
+ "TORRE",
1113
+ "CASTILLO",
1114
+ "PIRÁMIDE",
1115
+ "PLANETA",
1116
+ "GALAXIA",
1117
+ "SATÉLITE",
1118
+ "ASTRONAUTA",
1119
+ "TELESCOPIO",
1120
+ "MICROSCOPIO",
1121
+ "IMÁN",
1122
+ "BATERÍA", // duplicate
1123
+ "BOMBILLA",
1124
+ "ENCHUFE",
1125
+ "ENCHUFE", // PLUG (maybe “CLAVIJA,” but “ENCHUFE” is also used)
1126
+ "CABLE",
1127
+ "INTERRUPTOR",
1128
+ "CIRCUITO",
1129
+ "ROBOT",
1130
+ "ORDENADOR",
1131
+ "RATÓN",
1132
+ "TECLADO",
1133
+ "PANTALLA",
1134
+ "IMPRESORA",
1135
+ "ALTAVOZ",
1136
+ "AURICULARES",
1137
+ "TELÉFONO",
1138
+ "CÁMARA"
1139
+ ];
1140
+
1141
+ export const getRandomWord = (language: string = 'en') => {
1142
+ let wordList;
1143
+ switch (language) {
1144
+ case 'de':
1145
+ wordList = germanWords;
1146
+ break;
1147
+ case 'fr':
1148
+ wordList = frenchWords;
1149
+ break;
1150
+ case 'it':
1151
+ wordList = italianWords;
1152
+ break;
1153
+ case 'es':
1154
+ wordList = spanishWords;
1155
+ break;
1156
+ default:
1157
+ wordList = englishWords;
1158
+ }
1159
+ return wordList[Math.floor(Math.random() * wordList.length)];
1160
+ };
src/lib/words.ts DELETED
@@ -1,337 +0,0 @@
1
- export const englishWords = [
2
- "DOG",
3
- "CAT",
4
- "SUN",
5
- "RAIN",
6
- "TREE",
7
- "STAR",
8
- "MOON",
9
- "FISH",
10
- "BIRD",
11
- "CLOUD",
12
- "SKY",
13
- "WIND",
14
- "SNOW",
15
- "FLOWER",
16
- "BUTTERFLY",
17
- "WATER",
18
- "OCEAN",
19
- "RIVER",
20
- "MOUNTAIN",
21
- "FOREST",
22
- "HOUSE",
23
- "CANDLE",
24
- "GARDEN",
25
- "BRIDGE",
26
- "ISLAND",
27
- "BREEZE",
28
- "LIGHT",
29
- "THUNDER",
30
- "RAINBOW",
31
- "SMILE",
32
- "FRIEND",
33
- "FAMILY",
34
- "APPLE",
35
- "BANANA",
36
- "CAR",
37
- "BOAT",
38
- "BALL",
39
- "CAKE",
40
- "FROG",
41
- "HORSE",
42
- "LION",
43
- "MONKEY",
44
- "PANDA",
45
- "PLANE",
46
- "TRAIN",
47
- "CANDY",
48
- "JUMP",
49
- "PLAY",
50
- "SLEEP",
51
- "LAUGH",
52
- "DREAM",
53
- "HAPPY",
54
- "FUN",
55
- "COLOR",
56
- "BRIGHT",
57
- "WISH",
58
- "LOVE",
59
- "PEACE",
60
- "HUG",
61
- "KISS",
62
- "ZOO",
63
- "PARK",
64
- "BEACH",
65
- "TOY",
66
- "BOOK",
67
- "BUBBLE",
68
- "SHELL",
69
- "PEN",
70
- "ICE",
71
- "CAKE",
72
- "HAT",
73
- "SHOE",
74
- "CLOCK",
75
- "BED",
76
- "CUP",
77
- "KEY",
78
- "DOOR",
79
- "CHICKEN",
80
- "DUCK",
81
- "SHEEP",
82
- "COW",
83
- "PIG",
84
- "GOAT",
85
- "FOX",
86
- "BEAR",
87
- "DEER",
88
- "OWL",
89
- "EGG",
90
- "NEST",
91
- "ROCK",
92
- "LEAF",
93
- "BRUSH",
94
- "TOOTH",
95
- "HAND",
96
- "FEET",
97
- "EYE",
98
- "NOSE",
99
- "EAR",
100
- "MOUTH",
101
- "CHILD",
102
- "KITE",
103
- "BALLON"
104
- ];
105
-
106
- export const germanWords = [
107
- "HUND",
108
- "KATZE",
109
- "SONNE",
110
- "REGEN",
111
- "BAUM",
112
- "STERN",
113
- "MOND",
114
- "FISCH",
115
- "VOGEL",
116
- "WOLKE",
117
- "HIMMEL",
118
- "WIND",
119
- "SCHNEE",
120
- "BLUME",
121
- "WASSER",
122
- "OZEAN",
123
- "FLUSS",
124
- "BERG",
125
- "WALD",
126
- "HAUS",
127
- "KERZE",
128
- "GARTEN",
129
- "BRÜCKE",
130
- "INSEL",
131
- "LICHT",
132
- "DONNER",
133
- "LÄCHELN",
134
- "FREUND",
135
- "FAMILIE",
136
- "APFEL",
137
- "BANANE",
138
- "AUTO",
139
- "BOOT",
140
- "BALL",
141
- "KUCHEN",
142
- "FROSCH",
143
- "PFERD",
144
- "LÖWE",
145
- "AFFE",
146
- "PANDA",
147
- "FLUGZEUG",
148
- "ZUG",
149
- "BONBON",
150
- "SPRINGEN",
151
- "SPIELEN",
152
- "SCHLAFEN",
153
- "LACHEN",
154
- "TRAUM",
155
- "GLÜCK",
156
- "FARBE"
157
- ];
158
-
159
- export const frenchWords = [
160
- "CHIEN",
161
- "CHAT",
162
- "SOLEIL",
163
- "PLUIE",
164
- "ARBRE",
165
- "ÉTOILE",
166
- "LUNE",
167
- "POISSON",
168
- "OISEAU",
169
- "NUAGE",
170
- "CIEL",
171
- "VENT",
172
- "NEIGE",
173
- "FLEUR",
174
- "EAU",
175
- "OCÉAN",
176
- "RIVIÈRE",
177
- "MONTAGNE",
178
- "FORÊT",
179
- "MAISON",
180
- "BOUGIE",
181
- "JARDIN",
182
- "PONT",
183
- "ÎLE",
184
- "LUMIÈRE",
185
- "TONNERRE",
186
- "SOURIRE",
187
- "AMI",
188
- "FAMILLE",
189
- "POMME",
190
- "BANANE",
191
- "VOITURE",
192
- "BATEAU",
193
- "BALLON",
194
- "GÂTEAU",
195
- "GRENOUILLE",
196
- "CHEVAL",
197
- "LION",
198
- "SINGE",
199
- "PANDA",
200
- "AVION",
201
- "TRAIN",
202
- "BONBON",
203
- "SAUTER",
204
- "JOUER",
205
- "DORMIR",
206
- "RIRE",
207
- "RÊVE",
208
- "BONHEUR",
209
- "COULEUR"
210
- ];
211
-
212
- export const italianWords = [
213
- "CANE",
214
- "GATTO",
215
- "SOLE",
216
- "PIOGGIA",
217
- "ALBERO",
218
- "STELLA",
219
- "LUNA",
220
- "PESCE",
221
- "UCCELLO",
222
- "NUVOLA",
223
- "CIELO",
224
- "VENTO",
225
- "NEVE",
226
- "FIORE",
227
- "ACQUA",
228
- "OCEANO",
229
- "FIUME",
230
- "MONTAGNA",
231
- "FORESTA",
232
- "CASA",
233
- "CANDELA",
234
- "GIARDINO",
235
- "PONTE",
236
- "ISOLA",
237
- "LUCE",
238
- "TUONO",
239
- "SORRISO",
240
- "AMICO",
241
- "FAMIGLIA",
242
- "MELA",
243
- "BANANA",
244
- "AUTO",
245
- "BARCA",
246
- "PALLA",
247
- "TORTA",
248
- "RANA",
249
- "CAVALLO",
250
- "LEONE",
251
- "SCIMMIA",
252
- "PANDA",
253
- "AEREO",
254
- "TRENO",
255
- "CARAMELLA",
256
- "SALTARE",
257
- "GIOCARE",
258
- "DORMIRE",
259
- "RIDERE",
260
- "SOGNO",
261
- "FELICITÀ",
262
- "COLORE"
263
- ];
264
-
265
- export const spanishWords = [
266
- "PERRO",
267
- "GATO",
268
- "SOL",
269
- "LLUVIA",
270
- "ÁRBOL",
271
- "ESTRELLA",
272
- "LUNA",
273
- "PEZ",
274
- "PÁJARO",
275
- "NUBE",
276
- "CIELO",
277
- "VIENTO",
278
- "NIEVE",
279
- "FLOR",
280
- "AGUA",
281
- "OCÉANO",
282
- "RÍO",
283
- "MONTAÑA",
284
- "BOSQUE",
285
- "CASA",
286
- "VELA",
287
- "JARDÍN",
288
- "PUENTE",
289
- "ISLA",
290
- "LUZ",
291
- "TRUENO",
292
- "SONRISA",
293
- "AMIGO",
294
- "FAMILIA",
295
- "MANZANA",
296
- "PLÁTANO",
297
- "COCHE",
298
- "BARCO",
299
- "PELOTA",
300
- "PASTEL",
301
- "RANA",
302
- "CABALLO",
303
- "LEÓN",
304
- "MONO",
305
- "PANDA",
306
- "AVIÓN",
307
- "TREN",
308
- "CARAMELO",
309
- "SALTAR",
310
- "JUGAR",
311
- "DORMIR",
312
- "REÍR",
313
- "SUEÑO",
314
- "FELICIDAD",
315
- "COLOR"
316
- ];
317
-
318
- export const getRandomWord = (language: string = 'en') => {
319
- let wordList;
320
- switch (language) {
321
- case 'de':
322
- wordList = germanWords;
323
- break;
324
- case 'fr':
325
- wordList = frenchWords;
326
- break;
327
- case 'it':
328
- wordList = italianWords;
329
- break;
330
- case 'es':
331
- wordList = spanishWords;
332
- break;
333
- default:
334
- wordList = englishWords;
335
- }
336
- return wordList[Math.floor(Math.random() * wordList.length)];
337
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
supabase/functions/submit-high-score/index.ts ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createClient } from 'https://esm.sh/@supabase/[email protected]'
2
+
3
+ const corsHeaders = {
4
+ 'Access-Control-Allow-Origin': '*',
5
+ 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
6
+ }
7
+
8
+ Deno.serve(async (req) => {
9
+ if (req.method === 'OPTIONS') {
10
+ return new Response(null, { headers: corsHeaders })
11
+ }
12
+
13
+ try {
14
+ const { playerName, score, avgWordsPerRound, sessionId, theme } = await req.json()
15
+
16
+ if (!playerName || !score || !avgWordsPerRound || !sessionId || !theme) {
17
+ throw new Error('Missing required fields')
18
+ }
19
+
20
+ const supabaseClient = createClient(
21
+ Deno.env.get('SUPABASE_URL') ?? '',
22
+ Deno.env.get('SUPABASE_ANON_KEY') ?? '',
23
+ {
24
+ auth: {
25
+ persistSession: false,
26
+ },
27
+ }
28
+ )
29
+
30
+ console.log('Submitting score:', { playerName, score, avgWordsPerRound, sessionId, theme })
31
+
32
+ const { data, error } = await supabaseClient.rpc('check_and_update_high_score', {
33
+ p_player_name: playerName,
34
+ p_score: score,
35
+ p_avg_words_per_round: avgWordsPerRound,
36
+ p_session_id: sessionId,
37
+ p_theme: theme
38
+ })
39
+
40
+ if (error) {
41
+ throw error
42
+ }
43
+
44
+ return new Response(
45
+ JSON.stringify({ success: true, data }),
46
+ {
47
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
48
+ status: 200,
49
+ },
50
+ )
51
+ } catch (error) {
52
+ console.error('Error:', error.message)
53
+ return new Response(
54
+ JSON.stringify({ error: error.message }),
55
+ {
56
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
57
+ status: 400,
58
+ },
59
+ )
60
+ }
61
+ })
supabase/functions/validate-sentence/index.ts DELETED
@@ -1,56 +0,0 @@
1
- import { serve } from "https://deno.land/[email protected]/http/server.ts";
2
- import { Mistral } from "npm:@mistralai/mistralai";
3
-
4
- const corsHeaders = {
5
- 'Access-Control-Allow-Origin': '*',
6
- 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
7
- };
8
-
9
- serve(async (req) => {
10
- if (req.method === 'OPTIONS') {
11
- return new Response(null, { headers: corsHeaders });
12
- }
13
-
14
- try {
15
- const { sentence } = await req.json();
16
- console.log('Validating sentence:', sentence);
17
-
18
- const client = new Mistral({
19
- apiKey: Deno.env.get('MISTRAL_API_KEY'),
20
- });
21
-
22
- const response = await client.chat.complete({
23
- model: "mistral-large-latest",
24
- messages: [
25
- {
26
- role: "system",
27
- content: `You are an English language validator. Your task is to determine if the given sentence is grammatically correct and makes sense in English.
28
- Respond with ONLY "true" or "false".`
29
- },
30
- {
31
- role: "user",
32
- content: `Is this a valid, grammatically correct English sentence that makes sense: "${sentence}"?`
33
- }
34
- ],
35
- maxTokens: 10,
36
- temperature: 0.1
37
- });
38
-
39
- const isValid = response.choices[0].message.content.trim().toLowerCase() === 'true';
40
- console.log('Sentence validation result:', isValid);
41
-
42
- return new Response(
43
- JSON.stringify({ isValid }),
44
- { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
45
- );
46
- } catch (error) {
47
- console.error('Error validating sentence:', error);
48
- return new Response(
49
- JSON.stringify({ error: error.message }),
50
- {
51
- status: 500,
52
- headers: { ...corsHeaders, 'Content-Type': 'application/json' }
53
- }
54
- );
55
- }
56
- });