diff --git a/src/routes/stats/+page.server.ts b/src/routes/stats/+page.server.ts
new file mode 100644
index 00000000..de7fcc9b
--- /dev/null
+++ b/src/routes/stats/+page.server.ts
@@ -0,0 +1,61 @@
+import { fail, redirect } from '@sveltejs/kit';
+import type { PageServerLoad } from './$types';
+
+export const load: PageServerLoad = async ({ locals: { getSession, supabase } }) => {
+ const session = await getSession();
+ if (!session) {
+ redirect(303, '/auth/login');
+ }
+
+ const userId = session.user.id;
+ if (!userId) return fail(401, { message: 'User not found' });
+
+ const { data: games, error } = await supabase
+ .from('games')
+ .select(
+ 'id, creator, code, end_at, name, participation ( profile_id, score, total_score, nickname, nickname_image_url )'
+ );
+
+ if (error) {
+ return fail(500, { message: error });
+ }
+
+ const participatedGames = games
+ .filter((game) => game.participation.some((p) => p.profile_id === userId))
+ .sort((a, b) => new Date(b.end_at).getTime() - new Date(a.end_at).getTime());
+
+ const allParticipations = participatedGames.flatMap((game) =>
+ game.participation.filter((p) => p.profile_id === userId)
+ );
+
+ const allScores = allParticipations.flatMap((p) => p.score);
+
+ const totalScoreAcrossGames = allScores.reduce((acc, score) => acc + score, 0);
+
+ const average2FAScore = totalScoreAcrossGames / allScores.length;
+
+ const averageTotalScore =
+ allParticipations.reduce((acc, p) => acc + p.total_score, 0) / allParticipations.length;
+
+ const median2FAscore = allScores.sort((a, b) => a - b)[Math.floor(allScores.length / 2)];
+
+ const wins = participatedGames
+ .map((game) => {
+ const highscoreList = game.participation.sort((a, b) => b.total_score - a.total_score);
+ return highscoreList[0].profile_id === userId;
+ })
+ .filter(Boolean).length;
+
+ return {
+ stats: {
+ numberOfGames: participatedGames.length,
+ allScores,
+ totalScoreAcrossGames,
+ average2FAScore,
+ averageTotalScore,
+ median2FAscore,
+ wins
+ },
+ title: 'Stats'
+ };
+};
diff --git a/src/routes/stats/+page.svelte b/src/routes/stats/+page.svelte
new file mode 100644
index 00000000..bb5847c5
--- /dev/null
+++ b/src/routes/stats/+page.svelte
@@ -0,0 +1,82 @@
+
+
+
+ {#if numberOfGames}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {:else}
+
+
+
No past games found
+
Get started by creating a new AuthentiClash session.
+
+
+ {/if}
+
diff --git a/src/routes/stats/StatsCard.svelte b/src/routes/stats/StatsCard.svelte
new file mode 100644
index 00000000..1785735f
--- /dev/null
+++ b/src/routes/stats/StatsCard.svelte
@@ -0,0 +1,24 @@
+
+
+
+
{title}
+
+
diff --git a/src/routes/stats/StatsNumber.svelte b/src/routes/stats/StatsNumber.svelte
new file mode 100644
index 00000000..09701d7b
--- /dev/null
+++ b/src/routes/stats/StatsNumber.svelte
@@ -0,0 +1,11 @@
+
+
+
{currentNum.toFixed(decimals)}