diff --git a/.gitignore b/.gitignore
index 0710e51c..469a3bd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
node_modules
.env
.vscode
+.mcp.json
*.tsbuildinfo
.tsbuildinfo
diff --git a/.playwright-mcp/page-2026-01-04T16-08-22-788Z.png b/.playwright-mcp/page-2026-01-04T16-08-22-788Z.png
new file mode 100644
index 00000000..33a81d70
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-08-22-788Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-10-49-313Z.png b/.playwright-mcp/page-2026-01-04T16-10-49-313Z.png
new file mode 100644
index 00000000..b1af83da
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-10-49-313Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-11-14-268Z.png b/.playwright-mcp/page-2026-01-04T16-11-14-268Z.png
new file mode 100644
index 00000000..a3440867
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-11-14-268Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-13-19-566Z.png b/.playwright-mcp/page-2026-01-04T16-13-19-566Z.png
new file mode 100644
index 00000000..d781e552
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-13-19-566Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-14-49-087Z.png b/.playwright-mcp/page-2026-01-04T16-14-49-087Z.png
new file mode 100644
index 00000000..6f375a29
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-14-49-087Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-26-49-577Z.png b/.playwright-mcp/page-2026-01-04T16-26-49-577Z.png
new file mode 100644
index 00000000..8322d798
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-26-49-577Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-27-55-291Z.png b/.playwright-mcp/page-2026-01-04T16-27-55-291Z.png
new file mode 100644
index 00000000..8322d798
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-27-55-291Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-28-43-783Z.png b/.playwright-mcp/page-2026-01-04T16-28-43-783Z.png
new file mode 100644
index 00000000..8322d798
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-28-43-783Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-31-22-308Z.png b/.playwright-mcp/page-2026-01-04T16-31-22-308Z.png
new file mode 100644
index 00000000..210b24f7
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-31-22-308Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-36-23-230Z.png b/.playwright-mcp/page-2026-01-04T16-36-23-230Z.png
new file mode 100644
index 00000000..cec7e198
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-36-23-230Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-37-45-837Z.png b/.playwright-mcp/page-2026-01-04T16-37-45-837Z.png
new file mode 100644
index 00000000..210b24f7
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-37-45-837Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-52-23-837Z.png b/.playwright-mcp/page-2026-01-04T16-52-23-837Z.png
new file mode 100644
index 00000000..ee6e843c
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-52-23-837Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-53-01-868Z.png b/.playwright-mcp/page-2026-01-04T16-53-01-868Z.png
new file mode 100644
index 00000000..b23acb7a
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-53-01-868Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-55-04-138Z.png b/.playwright-mcp/page-2026-01-04T16-55-04-138Z.png
new file mode 100644
index 00000000..7dcd8c19
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-55-04-138Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T16-56-05-826Z.png b/.playwright-mcp/page-2026-01-04T16-56-05-826Z.png
new file mode 100644
index 00000000..da78b9de
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T16-56-05-826Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T17-01-22-690Z.png b/.playwright-mcp/page-2026-01-04T17-01-22-690Z.png
new file mode 100644
index 00000000..e72ca78f
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T17-01-22-690Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T17-02-15-302Z.png b/.playwright-mcp/page-2026-01-04T17-02-15-302Z.png
new file mode 100644
index 00000000..fe3232b3
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T17-02-15-302Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T18-10-29-487Z.png b/.playwright-mcp/page-2026-01-04T18-10-29-487Z.png
new file mode 100644
index 00000000..80be6dea
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T18-10-29-487Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T18-11-10-223Z.png b/.playwright-mcp/page-2026-01-04T18-11-10-223Z.png
new file mode 100644
index 00000000..dabcfca0
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T18-11-10-223Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T18-14-52-888Z.png b/.playwright-mcp/page-2026-01-04T18-14-52-888Z.png
new file mode 100644
index 00000000..5abad90f
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T18-14-52-888Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T18-15-13-460Z.png b/.playwright-mcp/page-2026-01-04T18-15-13-460Z.png
new file mode 100644
index 00000000..68bf0e10
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T18-15-13-460Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T18-16-07-536Z.png b/.playwright-mcp/page-2026-01-04T18-16-07-536Z.png
new file mode 100644
index 00000000..0b7975c3
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T18-16-07-536Z.png differ
diff --git a/.playwright-mcp/page-2026-01-04T18-17-28-398Z.png b/.playwright-mcp/page-2026-01-04T18-17-28-398Z.png
new file mode 100644
index 00000000..6783110b
Binary files /dev/null and b/.playwright-mcp/page-2026-01-04T18-17-28-398Z.png differ
diff --git a/packages/api/src/services/auth/devLogin.ts b/packages/api/src/services/auth/devLogin.ts
new file mode 100644
index 00000000..d93caa24
--- /dev/null
+++ b/packages/api/src/services/auth/devLogin.ts
@@ -0,0 +1,48 @@
+import crypto from 'crypto';
+import { db } from 'db';
+import createDebug from 'debug';
+
+const debug = createDebug('backend-ts:auth');
+
+function generateSessionId(): string {
+ return crypto.randomBytes(16).toString('hex');
+}
+
+export async function devLogin(playerId: number): Promise<{ session: string }> {
+ debug(`Dev login for player ID: ${playerId}`);
+
+ // Verify player exists
+ const player = await db
+ .selectFrom('players')
+ .select(['id', 'nickname'])
+ .where('id', '=', playerId)
+ .executeTakeFirst();
+
+ if (!player) {
+ throw new Error(`Player with ID ${playerId} not found`);
+ }
+
+ debug(`Found player: ${player.nickname} (${player.id})`);
+
+ // Delete old sessions for this player
+ await db.deleteFrom('sessions').where('player', '=', player.id).execute();
+
+ // Create new session (valid for 2 weeks)
+ const now = new Date();
+ const sessionId = generateSessionId();
+ const validUntil = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
+
+ await db
+ .insertInto('sessions')
+ .values({
+ id: sessionId,
+ player: player.id,
+ established: now,
+ valid_until: validUntil,
+ })
+ .execute();
+
+ debug(`Created dev session for player ${player.id}`);
+
+ return { session: sessionId };
+}
diff --git a/packages/api/src/trpc/routes/auth.ts b/packages/api/src/trpc/routes/auth.ts
index db080c0f..c99d41e5 100644
--- a/packages/api/src/trpc/routes/auth.ts
+++ b/packages/api/src/trpc/routes/auth.ts
@@ -1,8 +1,9 @@
+import { devLogin as devLoginService } from 'services/auth/devLogin';
import { getRegistrationToken as getRegistrationTokenService } from 'services/auth/getRegistrationToken';
import { loginWithGoogleCredential } from 'services/auth/googleLogin';
import { logout as logoutService } from 'services/auth/logout';
import { registerPlayer } from 'services/auth/register';
-import { publicProcedure, router } from 'trpc/trpc';
+import { devProcedure, publicProcedure, router } from 'trpc/trpc';
import { z } from 'zod';
export const loginGoogle = publicProcedure
@@ -44,9 +45,20 @@ export const logout = publicProcedure.mutation(async ({ ctx }) => {
}
});
+export const devLogin = devProcedure
+ .input(
+ z.object({
+ playerId: z.number(),
+ })
+ )
+ .mutation(({ input }) => {
+ return devLoginService(input.playerId);
+ });
+
export const auth = router({
loginGoogle,
getRegistrationToken,
register,
logout,
+ devLogin,
});
diff --git a/packages/api/src/trpc/trpc.ts b/packages/api/src/trpc/trpc.ts
index afa79c62..65b180dc 100644
--- a/packages/api/src/trpc/trpc.ts
+++ b/packages/api/src/trpc/trpc.ts
@@ -66,3 +66,13 @@ export const adminProcedure = t.procedure.use(async ({ ctx, next }) => {
}
return next({ ctx: { user: ctx.user } });
});
+
+/**
+ * Dev procedure - only available in development mode
+ */
+export const devProcedure = t.procedure.use(async ({ next }) => {
+ if (process.env.NODE_ENV !== 'development') {
+ throw new TRPCError({ code: 'FORBIDDEN', message: 'Only available in development mode' });
+ }
+ return next();
+});
diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx
index 565f725c..b918f2c7 100644
--- a/packages/web/src/App.tsx
+++ b/packages/web/src/App.tsx
@@ -12,6 +12,20 @@ import { queryClient } from 'utils/trpc';
const theme = createTheme({
autoContrast: true,
primaryColor: 'gray',
+ breakpoints: {
+ xs: '30em',
+ sm: '48em',
+ md: '64em',
+ lg: '74em',
+ xl: '90em',
+ },
+ lineHeights: {
+ xs: '1.2',
+ sm: '1.3',
+ md: '1.4',
+ lg: '1.5',
+ xl: '1.6',
+ },
spacing: {
xxs: '0.25rem',
xs: '0.625rem',
diff --git a/packages/web/src/components/Flag/Flag.tsx b/packages/web/src/components/Flag/Flag.tsx
index f029a3aa..b6ad23be 100644
--- a/packages/web/src/components/Flag/Flag.tsx
+++ b/packages/web/src/components/Flag/Flag.tsx
@@ -5,12 +5,13 @@ import './flag.scss';
interface FlagProps {
region: string;
className?: string;
+ size?: 'sm' | 'md';
}
-export const Flag = memo(function Flag({ region, className }: FlagProps) {
+export const Flag = memo(function Flag({ region, className, size = 'md' }: FlagProps) {
return (
diff --git a/packages/web/src/components/Flag/flag.scss b/packages/web/src/components/Flag/flag.scss
index 8d9d09bd..a2768027 100644
--- a/packages/web/src/components/Flag/flag.scss
+++ b/packages/web/src/components/Flag/flag.scss
@@ -1,6 +1,15 @@
.flag {
- width: 20px;
- height: 15px;
border-radius: 3px;
opacity: 0.85;
+
+ &--md {
+ width: 20px;
+ height: 15px;
+ }
+
+ &--sm {
+ width: 16px;
+ height: 12px;
+ border-radius: 2px;
+ }
}
diff --git a/packages/web/src/components/TopBar/top-bar.scss b/packages/web/src/components/TopBar/top-bar.scss
index a936ace9..5b01e05e 100644
--- a/packages/web/src/components/TopBar/top-bar.scss
+++ b/packages/web/src/components/TopBar/top-bar.scss
@@ -45,9 +45,9 @@
@media screen and (max-width: 500px) {
.top-bar > nav > ul {
- font-size: 4.4vw;
+ font-size: 16px;
> li > a {
- margin: 1vw;
+ margin: 4px;
}
}
}
diff --git a/packages/web/src/features/leaderboards/components/charts/Chart.tsx b/packages/web/src/features/leaderboards/components/charts/Chart.tsx
index 8b9dc8c1..b4968ac0 100644
--- a/packages/web/src/features/leaderboards/components/charts/Chart.tsx
+++ b/packages/web/src/features/leaderboards/components/charts/Chart.tsx
@@ -1,4 +1,4 @@
-import { ActionIcon, Text } from '@mantine/core';
+import { ActionIcon, Group, Text } from '@mantine/core';
import { useAtomValue } from 'jotai';
import _ from 'lodash/fp';
import { Fragment, memo, useState } from 'react';
@@ -9,8 +9,6 @@ import { useHighlightedPlayerIds } from 'features/leaderboards/hooks/useHighligh
import { useUser } from 'hooks/useUser';
-import { useLanguage } from 'utils/context/translation';
-
import type { ChartApiOutput } from '../../hooks/useChartsQuery';
import { ChartHeader } from './ChartHeader/ChartHeader';
import Result from './Result';
@@ -21,7 +19,6 @@ const Chart = memo(function _Chart({ chart }: { chart: ChartApiOutput }) {
const filter = useAtomValue(filterAtom);
const highlightedPlayerIds = useHighlightedPlayerIds();
const [showHidden, setShowHidden] = useState(false);
- const lang = useLanguage();
const hiddenResultsCount = chart.results.reduce((sum, res) => (res.isHidden ? sum + 1 : sum), 0);
@@ -96,18 +93,13 @@ const Chart = memo(function _Chart({ chart }: { chart: ChartApiOutput }) {