diff --git a/bun.lock b/bun.lock
index a5f208e..f655f59 100644
--- a/bun.lock
+++ b/bun.lock
@@ -31,8 +31,8 @@
"facehash": "^0.0.7",
"framer-motion": "^12.23.12",
"pocketbase": "^0.26.2",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
+ "react": "^19.3.0",
+ "react-dom": "^19.3.0",
"react-imask": "^7.6.1",
"redaxios": "^0.5.1",
"sonner": "^2.0.7",
@@ -49,8 +49,8 @@
"@lingui/cli": "^6.5.0",
"@lingui/vite-plugin": "^6.5.0",
"@types/node": "^22.5.4",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
+ "@types/react": "^19.3.0",
+ "@types/react-dom": "^19.3.0",
"@types/web-push": "^3.6.4",
"@vitejs/plugin-react": "^5.0.0",
"autoprefixer": "^10.4.20",
@@ -655,9 +655,9 @@
"@types/node": ["@types/node@22.19.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw=="],
- "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="],
+ "@types/react": ["@types/react@19.3.0", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="],
- "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
+ "@types/react-dom": ["@types/react-dom@19.3.0", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="],
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
@@ -1329,9 +1329,9 @@
"raf-schd": ["raf-schd@4.0.3", "", {}, "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="],
- "react": ["react@19.2.7", "", {}, "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ=="],
+ "react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="],
- "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="],
+ "react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="],
"react-imask": ["react-imask@7.6.1", "", { "dependencies": { "imask": "^7.6.1", "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=0.14.0" } }, "sha512-vLNfzcCz62Yzx/GRGh5tiCph9Gbh2cZu+Tz8OiO5it2eNuuhpA0DWhhSlOtVtSJ80+Bx+vFK5De8eQ9AmbkXzA=="],
@@ -1397,7 +1397,7 @@
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
- "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
+ "scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="],
"scmp": ["scmp@2.1.0", "", {}, "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q=="],
diff --git a/package.json b/package.json
index c7cb483..aa9e2f1 100644
--- a/package.json
+++ b/package.json
@@ -43,8 +43,8 @@
"facehash": "^0.0.7",
"framer-motion": "^12.23.12",
"pocketbase": "^0.26.2",
- "react": "^19.2.7",
- "react-dom": "^19.2.7",
+ "react": "^19.3.0",
+ "react-dom": "^19.3.0",
"react-imask": "^7.6.1",
"redaxios": "^0.5.1",
"sonner": "^2.0.7",
@@ -61,8 +61,8 @@
"@lingui/cli": "^6.5.0",
"@lingui/vite-plugin": "^6.5.0",
"@types/node": "^22.5.4",
- "@types/react": "^19.2.17",
- "@types/react-dom": "^19.2.3",
+ "@types/react": "^19.3.0",
+ "@types/react-dom": "^19.3.0",
"@types/web-push": "^3.6.4",
"@vitejs/plugin-react": "^5.0.0",
"autoprefixer": "^10.4.20",
diff --git a/public/styles.css b/public/styles.css
index 1e22569..276f238 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -50,7 +50,86 @@ textarea,
transform: scale(0.90) translateY(-4px);
}
+::view-transition-old(root),
+::view-transition-new(root),
+::view-transition-old(page),
+::view-transition-new(page),
+::view-transition-old(app-header),
+::view-transition-new(app-header) {
+ animation-duration: 180ms;
+ animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
+}
+
+html[data-nav-direction="forward"]::view-transition-old(page) {
+ animation: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-under-out;
+}
+html[data-nav-direction="forward"]::view-transition-new(page) {
+ animation: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-over-in;
+}
+html[data-nav-direction="back"]::view-transition-old(page) {
+ animation: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-over-out;
+ z-index: 1;
+}
+html[data-nav-direction="back"]::view-transition-new(page) {
+ animation: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-under-in;
+}
+
+html[data-nav-direction="forward"][data-header-vt="exit"]::view-transition-old(app-header) {
+ animation: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-header-out;
+}
+html[data-nav-direction="back"][data-header-vt="exit"]::view-transition-old(app-header) {
+ animation: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-over-out;
+}
+html[data-nav-direction="forward"][data-header-vt="enter"]::view-transition-new(app-header) {
+ animation: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-over-in;
+}
+html[data-nav-direction="back"][data-header-vt="enter"]::view-transition-new(app-header) {
+ animation: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-header-in;
+}
+
+@keyframes flxn-page-over-in {
+ from { transform: translateX(100%); }
+}
+@keyframes flxn-page-over-out {
+ to { transform: translateX(100%); }
+}
+@keyframes flxn-page-under-out {
+ to { transform: translateX(-25%); opacity: 0.8; }
+}
+@keyframes flxn-page-under-in {
+ from { transform: translateX(-25%); opacity: 0.8; }
+}
+
+@keyframes flxn-header-out {
+ 45% { opacity: 0; }
+ to { transform: translateX(-25%); opacity: 0; }
+}
+@keyframes flxn-header-in {
+ from { transform: translateX(-25%); opacity: 0; }
+}
+
+@keyframes flxn-vt-fade-out {
+ to { opacity: 0; }
+}
+@keyframes flxn-vt-fade-in {
+ from { opacity: 0; }
+}
+
@media (prefers-reduced-motion: reduce) {
+ ::view-transition-group(*),
+ ::view-transition-image-pair(*) {
+ animation: none !important;
+ }
+
+ ::view-transition-old(*) {
+ animation: 120ms ease both flxn-vt-fade-out !important;
+ z-index: auto !important;
+ }
+
+ ::view-transition-new(*) {
+ animation: 120ms ease both flxn-vt-fade-in !important;
+ }
+
.app,
[data-drawer-level] {
transition: none;
diff --git a/src/app/router.tsx b/src/app/router.tsx
index 655ea71..2ec5b70 100644
--- a/src/app/router.tsx
+++ b/src/app/router.tsx
@@ -3,6 +3,8 @@ import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
import { routeTree } from "./routeTree.gen";
import { DefaultCatchBoundary } from "../components/DefaultCatchBoundary";
+import RoutePending from "../components/route-pending";
+import { headerChange, navDirection } from "@/features/core/utils/nav-direction";
import { defaultHeaderConfig } from "@/features/core/hooks/use-router-config";
export function getRouter() {
@@ -30,6 +32,15 @@ export function getRouter() {
},
defaultPreload: "intent",
defaultPreloadStaleTime: 60_000,
+ defaultPendingMs: 100,
+ defaultPendingMinMs: 0,
+ defaultViewTransition: {
+ types: ({ fromLocation, toLocation }) => {
+ const direction = navDirection(fromLocation, toLocation);
+ return direction === "none" ? false : [direction];
+ },
+ },
+ defaultPendingComponent: RoutePending,
defaultErrorComponent: DefaultCatchBoundary,
});
@@ -38,6 +49,13 @@ export function getRouter() {
queryClient
})
+ if (typeof document !== "undefined") {
+ router.subscribe("onBeforeNavigate", ({ fromLocation, toLocation }) => {
+ document.documentElement.dataset.navDirection = navDirection(fromLocation, toLocation);
+ document.documentElement.dataset.headerVt = headerChange(fromLocation, toLocation);
+ });
+ }
+
return router;
}
diff --git a/src/app/routes/_authed/index.tsx b/src/app/routes/_authed/index.tsx
index 1faae50..4163fcc 100644
--- a/src/app/routes/_authed/index.tsx
+++ b/src/app/routes/_authed/index.tsx
@@ -1,7 +1,8 @@
import { createFileRoute } from "@tanstack/react-router";
import { tournamentQueries, useCurrentTournament } from "@/features/tournaments/queries";
import UpcomingTournament from "@/features/tournaments/components/upcoming-tournament";
-import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
+import { getServerQueryData } from "@/lib/tanstack-query/utils/ensure";
+import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
import StartedTournament from "@/features/tournaments/components/started-tournament";
import { Suspense } from "react";
import UpcomingTournamentSkeleton from "@/features/tournaments/components/upcoming-tournament/skeleton";
@@ -10,16 +11,13 @@ export const Route = createFileRoute("/_authed/")({
component: () => }>
,
- beforeLoad: async ({ context }) => {
- const queryClient = context.queryClient;
- const tournament = await ensureServerQueryData(queryClient, tournamentQueries.current())
-
- return { tournament }
+ beforeLoad: ({ context }) => {
+ prefetchServerQuery(context.queryClient, tournamentQueries.current());
},
loader: ({ context }) => ({
withPadding: false,
header: {
- title: context.tournament.name || "FLXN"
+ title: getServerQueryData(context.queryClient, tournamentQueries.current())?.name || "FLXN"
}
}),
pendingComponent: () =>
diff --git a/src/app/routes/_authed/profile.$playerId.tsx b/src/app/routes/_authed/profile.$playerId.tsx
index f9bf474..e2401d3 100644
--- a/src/app/routes/_authed/profile.$playerId.tsx
+++ b/src/app/routes/_authed/profile.$playerId.tsx
@@ -14,26 +14,12 @@ const searchSchema = z.object({
export const Route = createFileRoute("/_authed/profile/$playerId")({
validateSearch: searchSchema,
- beforeLoad: async ({ params, context }) => {
+ beforeLoad: ({ params, context }) => {
const { queryClient } = context;
- await Promise.all([
- prefetchServerQuery(
- queryClient,
- playerQueries.details(params.playerId)
- ),
- prefetchServerQuery(
- queryClient,
- playerQueries.matches(params.playerId)
- ),
- prefetchServerQuery(
- queryClient,
- playerQueries.stats(params.playerId)
- ),
- prefetchServerQuery(
- queryClient,
- badgeQueries.playerBadges(params.playerId)
- ),
- ]);
+ prefetchServerQuery(queryClient, playerQueries.details(params.playerId));
+ prefetchServerQuery(queryClient, playerQueries.matches(params.playerId));
+ prefetchServerQuery(queryClient, playerQueries.stats(params.playerId));
+ prefetchServerQuery(queryClient, badgeQueries.playerBadges(params.playerId));
},
loader: ({ params, context }) => ({
header: {
diff --git a/src/app/routes/_authed/teams.$teamId.tsx b/src/app/routes/_authed/teams.$teamId.tsx
index 5751be8..33d04b6 100644
--- a/src/app/routes/_authed/teams.$teamId.tsx
+++ b/src/app/routes/_authed/teams.$teamId.tsx
@@ -12,9 +12,9 @@ const searchSchema = z.object({
export const Route = createFileRoute("/_authed/teams/$teamId")({
validateSearch: searchSchema,
- beforeLoad: async ({ params, context }) => {
+ beforeLoad: ({ params, context }) => {
const { queryClient } = context;
- await prefetchServerQuery(queryClient, teamQueries.details(params.teamId));
+ prefetchServerQuery(queryClient, teamQueries.details(params.teamId));
},
loader: ({ params }) => ({
header: {
diff --git a/src/components/route-pending.tsx b/src/components/route-pending.tsx
new file mode 100644
index 0000000..d72b02f
--- /dev/null
+++ b/src/components/route-pending.tsx
@@ -0,0 +1,17 @@
+import { Skeleton, Stack } from "@mantine/core";
+
+const RoutePending = () => (
+
+ {Array.from({ length: 5 }).map((_, index) => (
+
+ ))}
+
+);
+
+export default RoutePending;
diff --git a/src/components/swipeable-tabs.tsx b/src/components/swipeable-tabs.tsx
index 03d6792..1430b70 100644
--- a/src/components/swipeable-tabs.tsx
+++ b/src/components/swipeable-tabs.tsx
@@ -6,7 +6,7 @@ import {
ReactNode,
useRef,
useCallback,
- useMemo,
+ startTransition,
} from "react";
import { useRouter } from "@tanstack/react-router";
@@ -48,6 +48,11 @@ function SwipeableTabs({
}, [search?.tab, tabs, defaultTab]);
const [activeTab, setActiveTab] = useState(getActiveTabFromUrl);
+ const [mountAll, setMountAll] = useState(false);
+ useEffect(() => {
+ const timeoutId = setTimeout(() => startTransition(() => setMountAll(true)), 400);
+ return () => clearTimeout(timeoutId);
+ }, []);
const [stuck, setStuck] = useState(false);
const sentinelRef = useRef(null);
const [rootRef, setRootRef] = useState(null);
@@ -225,7 +230,7 @@ function SwipeableTabs({
{tabs.map((tab, index) => (
- {tab.content}
+ {(mountAll || index === activeTab) ? tab.content : null}
))}
diff --git a/src/features/badges/components/badge-showcase.tsx b/src/features/badges/components/badge-showcase.tsx
index eab3042..6269e60 100644
--- a/src/features/badges/components/badge-showcase.tsx
+++ b/src/features/badges/components/badge-showcase.tsx
@@ -49,6 +49,7 @@ export const BadgeIcon = ({ badge, filled, size = 48 }: BadgeIconProps & { size?
width={size}
height={size}
loading="lazy"
+ decoding="async"
onError={() => setImageError(true)}
style={{
objectFit: 'contain',
diff --git a/src/features/core/components/header.tsx b/src/features/core/components/header.tsx
index 5a62822..e5c3e47 100644
--- a/src/features/core/components/header.tsx
+++ b/src/features/core/components/header.tsx
@@ -60,6 +60,7 @@ const Header = ({ collapsed, title, titleValues, withBackButton }: HeaderConfig)
alignItems: 'center',
justifyContent: 'center',
paddingTop: 'env(safe-area-inset-top, 0px)',
+ viewTransitionName: 'app-header',
}}
>
{ withBackButton && }
diff --git a/src/features/core/components/layout.tsx b/src/features/core/components/layout.tsx
index ab18cfb..fa6e017 100644
--- a/src/features/core/components/layout.tsx
+++ b/src/features/core/components/layout.tsx
@@ -1,23 +1,22 @@
import { AppShell, Box } from '@mantine/core';
import { PropsWithChildren } from 'react';
-import { useLocation } from '@tanstack/react-router';
import Header from './header';
import Navbar from './navbar';
import Pullable from './pullable';
import useVisualViewportSize from '../hooks/use-visual-viewport-size';
import useRouterConfig from '../hooks/use-router-config';
import Page from '@/components/page';
-import './route-transition.css';
const Layout: React.FC = ({ children }) => {
const { header, withPadding, fullWidth } = useRouterConfig();
const viewport = useVisualViewportSize();
- const location = useLocation();
return (
= ({ children }) => {
pos='relative'
h='100%'
mah='100%'
- pb={{ base: 'calc(65px + env(safe-area-inset-bottom, 0px))', sm: 0 }}
+ pb={{ base: 'calc(73px + env(safe-area-inset-bottom, 0px))', sm: 0 }}
px={{ base: 0.01, sm: 100, md: 200, lg: 300 }}
maw='100dvw'
style={{ overflow: 'hidden' }}
>
-
-
-
+ {/* pane box must be opaque and route-invariant or slides drift */}
+
+
+
{children}
-
-
-
+
+
+
diff --git a/src/features/core/components/nav-link/styles.module.css b/src/features/core/components/nav-link/styles.module.css
index 3472538..34e7210 100644
--- a/src/features/core/components/nav-link/styles.module.css
+++ b/src/features/core/components/nav-link/styles.module.css
@@ -11,11 +11,6 @@
-webkit-tap-highlight-color: transparent;
}
-.navLinkBox svg,
-.navLinkBox p {
- transition: color 150ms ease-out;
-}
-
@media (prefers-reduced-motion: no-preference) {
.navLinkBox {
transition: transform 150ms cubic-bezier(0.32, 0.72, 0, 1);
diff --git a/src/features/core/components/route-transition.css b/src/features/core/components/route-transition.css
deleted file mode 100644
index fb54244..0000000
--- a/src/features/core/components/route-transition.css
+++ /dev/null
@@ -1,11 +0,0 @@
-@media (prefers-reduced-motion: no-preference) {
- .flxn-route-enter {
- animation: flxn-route-fade-in 180ms ease-out;
- }
-}
-
-@keyframes flxn-route-fade-in {
- from {
- opacity: 0;
- }
-}
diff --git a/src/features/core/utils/nav-direction.ts b/src/features/core/utils/nav-direction.ts
new file mode 100644
index 0000000..7739e39
--- /dev/null
+++ b/src/features/core/utils/nav-direction.ts
@@ -0,0 +1,52 @@
+import type { ParsedLocation } from "@tanstack/react-router";
+
+export type NavDirection = "forward" | "back" | "none";
+
+const tabRank = (pathname: string): number | null => {
+ if (pathname === "/") return 0;
+ if (pathname.startsWith("/stats")) return 1;
+ if (pathname.startsWith("/tournaments")) return 2;
+ if (pathname.startsWith("/profile") || pathname.startsWith("/settings")) return 3;
+ if (pathname.startsWith("/admin")) return 4;
+ return null;
+};
+
+const depth = (pathname: string) => pathname.split("/").filter(Boolean).length;
+
+// Keep in sync with route loaders that set header.collapsed
+const headerHidden = (pathname: string) => {
+ const segs = pathname.split("/").filter(Boolean);
+ return segs.length === 2 && ["profile", "teams", "tournaments"].includes(segs[0]);
+};
+
+export function headerChange(
+ from: ParsedLocation | undefined,
+ to: ParsedLocation
+): "exit" | "enter" | "none" {
+ if (!from) return "none";
+ const fromHidden = headerHidden(from.pathname);
+ const toHidden = headerHidden(to.pathname);
+ if (fromHidden === toHidden) return "none";
+ return toHidden ? "exit" : "enter";
+}
+
+export function navDirection(
+ from: ParsedLocation | undefined,
+ to: ParsedLocation
+): NavDirection {
+ if (!from || from.pathname === to.pathname) return "none";
+
+ const fromRank = tabRank(from.pathname);
+ const toRank = tabRank(to.pathname);
+ if (fromRank !== null && toRank !== null && fromRank !== toRank) {
+ return toRank > fromRank ? "forward" : "back";
+ }
+
+ const fromIndex = from.state.__TSR_index;
+ const toIndex = to.state.__TSR_index;
+ if (Number.isFinite(fromIndex) && Number.isFinite(toIndex) && fromIndex !== toIndex) {
+ return toIndex > fromIndex ? "forward" : "back";
+ }
+
+ return depth(to.pathname) >= depth(from.pathname) ? "forward" : "back";
+}
diff --git a/src/lib/tanstack-query/utils/ensure.ts b/src/lib/tanstack-query/utils/ensure.ts
index b6e0fa3..07dcc6c 100644
--- a/src/lib/tanstack-query/utils/ensure.ts
+++ b/src/lib/tanstack-query/utils/ensure.ts
@@ -1,6 +1,16 @@
import { QueryClient, QueryKey } from "@tanstack/react-query";
import { ServerResult } from "../types";
+export function getServerQueryData(
+ queryClient: QueryClient,
+ query: {
+ queryKey: QueryKey;
+ queryFn: () => Promise>;
+ }
+): TData | undefined {
+ return queryClient.getQueryData(query.queryKey);
+}
+
export async function ensureServerQueryData(
queryClient: QueryClient,
query: {
diff --git a/src/locales/de/messages.po b/src/locales/de/messages.po
index d7d085e..deb0256 100644
--- a/src/locales/de/messages.po
+++ b/src/locales/de/messages.po
@@ -2091,7 +2091,7 @@ msgstr "Vorheriger Titel"
msgid "Profile"
msgstr "Profil"
-#: src/features/badges/components/badge-showcase.tsx:301
+#: src/features/badges/components/badge-showcase.tsx:302
msgid "Progress "
msgstr "Fortschritt "
diff --git a/src/locales/en/messages.po b/src/locales/en/messages.po
index c8f69ec..01f1dfd 100644
--- a/src/locales/en/messages.po
+++ b/src/locales/en/messages.po
@@ -2091,7 +2091,7 @@ msgstr "Previous track"
msgid "Profile"
msgstr "Profile"
-#: src/features/badges/components/badge-showcase.tsx:301
+#: src/features/badges/components/badge-showcase.tsx:302
msgid "Progress "
msgstr "Progress "
diff --git a/src/locales/es/messages.po b/src/locales/es/messages.po
index 0268021..e1e4804 100644
--- a/src/locales/es/messages.po
+++ b/src/locales/es/messages.po
@@ -2091,7 +2091,7 @@ msgstr "Pista anterior"
msgid "Profile"
msgstr "Perfil"
-#: src/features/badges/components/badge-showcase.tsx:301
+#: src/features/badges/components/badge-showcase.tsx:302
msgid "Progress "
msgstr "Progreso "
diff --git a/src/locales/ja/messages.po b/src/locales/ja/messages.po
index bda0837..8411f93 100644
--- a/src/locales/ja/messages.po
+++ b/src/locales/ja/messages.po
@@ -2091,7 +2091,7 @@ msgstr "前のトラック"
msgid "Profile"
msgstr "プロフィール"
-#: src/features/badges/components/badge-showcase.tsx:301
+#: src/features/badges/components/badge-showcase.tsx:302
msgid "Progress "
msgstr "進行状況 "