19 Commits
Author SHA1 Message Date
kyle eafe5c24cf Merge pull request 'Hotfix' (#27) from development into main
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 8s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 42s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m33s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m27s
Reviewed-on: #27
2026-09-21 12:27:34 -05:00
kyle dbfda83e14 Update src/features/login/hooks/use-create-user.ts
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 9s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 37s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m44s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m30s
2026-09-21 12:15:47 -05:00
kyle 8fde1c9362 Update src/features/login/hooks/use-consume-code.ts
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 9s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 43s
CI/CD Pipeline / Deploy to Kubernetes (push) Canceled after 0s
CI/CD Pipeline / Build and Push App Docker Image (push) Canceled after 12s
2026-09-21 12:15:00 -05:00
kyle 0493c08dae Merge pull request 'Development' (#26) from development into main
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 13s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 37s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 3m7s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m29s
Reviewed-on: #26
2026-09-14 03:28:42 -05:00
kyle 9febb8bb60 remove slide animation, use pill instead
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 9s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 38s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m11s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m31s
2026-09-14 00:53:24 -07:00
kyle a754fc0282 match cards h2h margins
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 11s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 37s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m11s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m30s
2026-09-11 11:53:27 -07:00
kyle 6f17a5d4a8 slide animation
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 7s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 44s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m10s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m31s
2026-09-11 10:28:45 -07:00
kyle 9ed3f7169e slide animation
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 13s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 31s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 3m25s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m29s
2026-09-11 08:32:42 -07:00
kyle c4a9d47180 slide animation
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 14s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 33s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 3m27s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m28s
2026-09-10 22:21:16 -07:00
kyle e55a167df2 Merge pull request 'Development' (#25) from development into main
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 9s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 40s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m7s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m29s
Reviewed-on: #25
2026-08-31 12:35:31 -05:00
kyle 8971748d5c i18n: refresh catalog line references
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 9s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 36s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m7s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m27s
2026-08-30 14:33:50 -05:00
kyle 0ac6636d08 h2h icon
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 16s
CI/CD Pipeline / i18n Catalog Check (push) Failing after 35s
CI/CD Pipeline / Build and Push App Docker Image (push) Skipped
CI/CD Pipeline / Deploy to Kubernetes (push) Skipped
2026-08-30 13:05:47 -05:00
kyle f6faf97cdf develpoer role
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 10s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 43s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m14s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m29s
2026-08-30 12:36:28 -05:00
kyle 2ae6269a15 Merge pull request 'Development' (#24) from development into main
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 14s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 55s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 3m34s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m26s
Reviewed-on: #24
2026-08-28 10:40:06 -05:00
kyle 6bb3ee4bc7 fix: render local-timezone dates client-side only
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 14s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 37s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m10s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m28s
- started-tournament header and tournament list formatted times in the
  server timezone during ssr, mismatching hydration on every load
2026-08-27 14:14:03 -07:00
kyle 7bda138d69 feat(telemetry): matchup and direction context in timelines; live error groups
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 9s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 47s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m18s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m29s
- h2h_viewed carries who-v-who, stats_sorted carries asc/desc via a
  timeline-only context prop
- error browser aggregates raw rows so new groups appear immediately
- catalogs re-extracted for shifted line references
2026-08-27 10:47:57 -07:00
kyle 9b51d61519 i18n: refresh catalog line references
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 8s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 42s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m8s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m28s
2026-08-27 10:32:14 -07:00
kyle f4396d9527 Merge pull request 'pwa fixes' (#23) from development into main
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 8s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 43s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 2m4s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m28s
Reviewed-on: #23
2026-08-24 00:04:44 -05:00
kyle 352fadadb5 Merge pull request 'Adding: themes, predictions, match reporting, ui fixes, sse fixes, badge fixes, match flow, i18n, match edits, perf, auth fix' (#22) from development into main
CI/CD Pipeline / i18n Catalog Check (push) Successful in 30s
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 36s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 1m53s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m30s
Reviewed-on: #22
2026-08-23 23:08:56 -05:00
63 changed files with 958 additions and 770 deletions
+9 -9
View File
@@ -31,8 +31,8 @@
"facehash": "^0.0.7", "facehash": "^0.0.7",
"framer-motion": "^12.23.12", "framer-motion": "^12.23.12",
"pocketbase": "^0.26.2", "pocketbase": "^0.26.2",
"react": "^19.2.7", "react": "^19.3.0",
"react-dom": "^19.2.7", "react-dom": "^19.3.0",
"react-imask": "^7.6.1", "react-imask": "^7.6.1",
"redaxios": "^0.5.1", "redaxios": "^0.5.1",
"sonner": "^2.0.7", "sonner": "^2.0.7",
@@ -49,8 +49,8 @@
"@lingui/cli": "^6.5.0", "@lingui/cli": "^6.5.0",
"@lingui/vite-plugin": "^6.5.0", "@lingui/vite-plugin": "^6.5.0",
"@types/node": "^22.5.4", "@types/node": "^22.5.4",
"@types/react": "^19.2.17", "@types/react": "^19.3.0",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.3.0",
"@types/web-push": "^3.6.4", "@types/web-push": "^3.6.4",
"@vitejs/plugin-react": "^5.0.0", "@vitejs/plugin-react": "^5.0.0",
"autoprefixer": "^10.4.20", "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/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=="], "@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=="], "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=="], "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=="], "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=="], "scmp": ["scmp@2.1.0", "", {}, "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q=="],
+4 -4
View File
@@ -43,8 +43,8 @@
"facehash": "^0.0.7", "facehash": "^0.0.7",
"framer-motion": "^12.23.12", "framer-motion": "^12.23.12",
"pocketbase": "^0.26.2", "pocketbase": "^0.26.2",
"react": "^19.2.7", "react": "^19.3.0",
"react-dom": "^19.2.7", "react-dom": "^19.3.0",
"react-imask": "^7.6.1", "react-imask": "^7.6.1",
"redaxios": "^0.5.1", "redaxios": "^0.5.1",
"sonner": "^2.0.7", "sonner": "^2.0.7",
@@ -61,8 +61,8 @@
"@lingui/cli": "^6.5.0", "@lingui/cli": "^6.5.0",
"@lingui/vite-plugin": "^6.5.0", "@lingui/vite-plugin": "^6.5.0",
"@types/node": "^22.5.4", "@types/node": "^22.5.4",
"@types/react": "^19.2.17", "@types/react": "^19.3.0",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.3.0",
"@types/web-push": "^3.6.4", "@types/web-push": "^3.6.4",
"@vitejs/plugin-react": "^5.0.0", "@vitejs/plugin-react": "^5.0.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
-1
View File
@@ -13,7 +13,6 @@ const { count, size, warnings } = await generateSW({
'icon-192x192.png', 'icon-192x192.png',
'icon-512x512.png', 'icon-512x512.png',
'site.webmanifest', 'site.webmanifest',
'styles.css',
'push-handlers.js', 'push-handlers.js',
], ],
// Hand-written Web Push handlers (public/push-handlers.js) are pulled into // Hand-written Web Push handlers (public/push-handlers.js) are pulled into
+17 -8
View File
@@ -1,5 +1,5 @@
// Grant or revoke the "Admin" role for a user by phone number (US +1 assumed). // Grant or revoke a role for a user by phone number (US +1 assumed).
// Usage: bun run scripts/make-admin.ts <phone> [--revoke] // Usage: bun run scripts/make-admin.ts <phone> [--role Admin|Developer] [--revoke]
import "dotenv/config"; import "dotenv/config";
import SuperTokens from "supertokens-node"; import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session"; import Session from "supertokens-node/recipe/session";
@@ -26,10 +26,19 @@ SuperTokens.init({
], ],
}); });
const VALID_ROLES = ["Admin", "Developer"] as const;
const raw = process.argv[2]; const raw = process.argv[2];
const revoke = process.argv.includes("--revoke"); const revoke = process.argv.includes("--revoke");
const roleFlagIndex = process.argv.indexOf("--role");
const role = roleFlagIndex === -1 ? "Admin" : process.argv[roleFlagIndex + 1];
if (!raw) { if (!raw) {
console.error("Usage: bun run scripts/make-admin.ts <phone> [--revoke]"); console.error("Usage: bun run scripts/make-admin.ts <phone> [--role Admin|Developer] [--revoke]");
process.exit(1);
}
if (!VALID_ROLES.includes(role as (typeof VALID_ROLES)[number])) {
console.error(`Invalid role "${role}". Valid roles: ${VALID_ROLES.join(", ")}`);
process.exit(1); process.exit(1);
} }
@@ -60,15 +69,15 @@ if (!user) {
process.exit(1); process.exit(1);
} }
await UserRoles.createNewRoleOrAddPermissions("Admin", []); await UserRoles.createNewRoleOrAddPermissions(role, []);
if (revoke) { if (revoke) {
const res = await UserRoles.removeUserRole("public", user.id, "Admin"); const res = await UserRoles.removeUserRole("public", user.id, role);
console.log(`Removed Admin from ${matched} (user ${user.id}):`, res.status); console.log(`Removed ${role} from ${matched} (user ${user.id}):`, res.status);
} else { } else {
const res = await UserRoles.addRoleToUser("public", user.id, "Admin"); const res = await UserRoles.addRoleToUser("public", user.id, role);
console.log( console.log(
`Granted Admin to ${matched} (user ${user.id}):`, `Granted ${role} to ${matched} (user ${user.id}):`,
res.status === "OK" res.status === "OK"
? res.didUserAlreadyHaveRole ? res.didUserAlreadyHaveRole
? "already had it" ? "already had it"
+4
View File
@@ -3,6 +3,7 @@ import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query"; import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
import { routeTree } from "./routeTree.gen"; import { routeTree } from "./routeTree.gen";
import { DefaultCatchBoundary } from "../components/DefaultCatchBoundary"; import { DefaultCatchBoundary } from "../components/DefaultCatchBoundary";
import RoutePending from "../components/route-pending";
import { defaultHeaderConfig } from "@/features/core/hooks/use-router-config"; import { defaultHeaderConfig } from "@/features/core/hooks/use-router-config";
export function getRouter() { export function getRouter() {
@@ -30,6 +31,9 @@ export function getRouter() {
}, },
defaultPreload: "intent", defaultPreload: "intent",
defaultPreloadStaleTime: 60_000, defaultPreloadStaleTime: 60_000,
defaultPendingMs: 100,
defaultPendingMinMs: 0,
defaultPendingComponent: RoutePending,
defaultErrorComponent: DefaultCatchBoundary, defaultErrorComponent: DefaultCatchBoundary,
}); });
+16 -2
View File
@@ -31,6 +31,7 @@ import { getRootMeta } from "@/lib/i18n/meta";
import FullScreenLoader from "@/components/full-screen-loader"; import FullScreenLoader from "@/components/full-screen-loader";
import { CHROME_COLORS } from "@/lib/mantine/theme-colors"; import { CHROME_COLORS } from "@/lib/mantine/theme-colors";
import mantineCssUrl from '@mantine/core/styles.css?url' import mantineCssUrl from '@mantine/core/styles.css?url'
import appStylesUrl from '../styles.css?url'
import mantineDatesCssUrl from '@mantine/dates/styles.css?url' import mantineDatesCssUrl from '@mantine/dates/styles.css?url'
import mantineCarouselCssUrl from '@mantine/carousel/styles.css?url' import mantineCarouselCssUrl from '@mantine/carousel/styles.css?url'
import mantineTiptapCssUrl from '@mantine/tiptap/styles.css?url' import mantineTiptapCssUrl from '@mantine/tiptap/styles.css?url'
@@ -101,6 +102,7 @@ export const Route = createRootRouteWithContext<{
}, },
{ rel: "manifest", href: "/site.webmanifest" }, { rel: "manifest", href: "/site.webmanifest" },
{ rel: "icon", href: "/favicon.ico" }, { rel: "icon", href: "/favicon.ico" },
{ rel: 'stylesheet', href: appStylesUrl },
{ rel: 'stylesheet', href: mantineCssUrl }, { rel: 'stylesheet', href: mantineCssUrl },
{ rel: 'stylesheet', href: mantineCarouselCssUrl }, { rel: 'stylesheet', href: mantineCarouselCssUrl },
{ rel: 'stylesheet', href: mantineDatesCssUrl }, { rel: 'stylesheet', href: mantineDatesCssUrl },
@@ -183,7 +185,19 @@ function RootComponent() {
ensureSuperTokensFrontend(); ensureSuperTokensFrontend();
initTelemetry(router); initTelemetry(router);
if (import.meta.env.PROD && 'serviceWorker' in navigator) { if (import.meta.env.PROD && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').catch(() => track('sw_register_failed')); // first install claims the page too; only reload on real updates
const hadController = !!navigator.serviceWorker.controller;
let reloading = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (!hadController || reloading) return;
reloading = true;
window.location.reload();
});
navigator.serviceWorker.register('/sw.js').then((registration) => {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') registration.update().catch(() => {});
});
}).catch(() => track('sw_register_failed'));
} }
}, [router]); }, [router]);
@@ -231,7 +245,6 @@ function RootDocument({ children }: { children: React.ReactNode }) {
'try{var d=document.documentElement,t=localStorage.getItem("flxn-theme");if(t&&t!=="default"){d.setAttribute("data-theme",t);}var s=localStorage.getItem("flxn-theme-scheme");if(s){d.setAttribute("data-mantine-color-scheme",s);}var f=JSON.parse(localStorage.getItem("flxn-theme-fonts")||"[]");for(var i=0;i<f.length;i++){var l=document.createElement("link");l.rel="stylesheet";l.href=f[i];l.setAttribute("data-flxn-theme-font",t||"default");document.head.appendChild(l);}}catch(e){}', 'try{var d=document.documentElement,t=localStorage.getItem("flxn-theme");if(t&&t!=="default"){d.setAttribute("data-theme",t);}var s=localStorage.getItem("flxn-theme-scheme");if(s){d.setAttribute("data-mantine-color-scheme",s);}var f=JSON.parse(localStorage.getItem("flxn-theme-fonts")||"[]");for(var i=0;i<f.length;i++){var l=document.createElement("link");l.rel="stylesheet";l.href=f[i];l.setAttribute("data-flxn-theme-font",t||"default");document.head.appendChild(l);}}catch(e){}',
}} }}
/> />
<link rel="stylesheet" href="/styles.css" />
</head> </head>
<body <body
style={{ style={{
@@ -243,6 +256,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
}} }}
> >
<div className="app">{children}</div> <div className="app">{children}</div>
<div className="flxn-theme-overlay" aria-hidden="true" />
<Scripts /> <Scripts />
</body> </body>
</html> </html>
+7 -1
View File
@@ -1,7 +1,8 @@
import { Outlet, createFileRoute, useNavigate, useRouterState } from "@tanstack/react-router"; import { Outlet, createFileRoute, redirect, useNavigate, useRouterState } from "@tanstack/react-router";
import { Tabs } from "@mantine/core"; import { Tabs } from "@mantine/core";
import { Trans } from "@lingui/react/macro"; import { Trans } from "@lingui/react/macro";
import { msg } from "@lingui/core/macro"; import { msg } from "@lingui/core/macro";
import { isDeveloperRole } from "@/features/core/utils/roles";
const TAB_ROUTES = { const TAB_ROUTES = {
usage: "/admin/telemetry/usage", usage: "/admin/telemetry/usage",
@@ -15,6 +16,11 @@ type TabValue = keyof typeof TAB_ROUTES;
export const Route = createFileRoute("/_authed/admin/telemetry")({ export const Route = createFileRoute("/_authed/admin/telemetry")({
component: TelemetryLayout, component: TelemetryLayout,
beforeLoad: ({ context }) => {
if (!isDeveloperRole(context.auth?.roles)) {
throw redirect({ to: "/admin" });
}
},
loader: () => ({ loader: () => ({
header: { header: {
title: msg`Telemetry`, title: msg`Telemetry`,
+5 -7
View File
@@ -1,7 +1,8 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { tournamentQueries, useCurrentTournament } from "@/features/tournaments/queries"; import { tournamentQueries, useCurrentTournament } from "@/features/tournaments/queries";
import UpcomingTournament from "@/features/tournaments/components/upcoming-tournament"; 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 StartedTournament from "@/features/tournaments/components/started-tournament";
import { Suspense } from "react"; import { Suspense } from "react";
import UpcomingTournamentSkeleton from "@/features/tournaments/components/upcoming-tournament/skeleton"; import UpcomingTournamentSkeleton from "@/features/tournaments/components/upcoming-tournament/skeleton";
@@ -10,16 +11,13 @@ export const Route = createFileRoute("/_authed/")({
component: () => <Suspense fallback={<UpcomingTournamentSkeleton />}> component: () => <Suspense fallback={<UpcomingTournamentSkeleton />}>
<Home /> <Home />
</Suspense>, </Suspense>,
beforeLoad: async ({ context }) => { beforeLoad: ({ context }) => {
const queryClient = context.queryClient; prefetchServerQuery(context.queryClient, tournamentQueries.current());
const tournament = await ensureServerQueryData(queryClient, tournamentQueries.current())
return { tournament }
}, },
loader: ({ context }) => ({ loader: ({ context }) => ({
withPadding: false, withPadding: false,
header: { header: {
title: context.tournament.name || "FLXN" title: getServerQueryData(context.queryClient, tournamentQueries.current())?.name || "FLXN"
} }
}), }),
pendingComponent: () => <UpcomingTournamentSkeleton /> pendingComponent: () => <UpcomingTournamentSkeleton />
+5 -19
View File
@@ -14,26 +14,12 @@ const searchSchema = z.object({
export const Route = createFileRoute("/_authed/profile/$playerId")({ export const Route = createFileRoute("/_authed/profile/$playerId")({
validateSearch: searchSchema, validateSearch: searchSchema,
beforeLoad: async ({ params, context }) => { beforeLoad: ({ params, context }) => {
const { queryClient } = context; const { queryClient } = context;
await Promise.all([ prefetchServerQuery(queryClient, playerQueries.details(params.playerId));
prefetchServerQuery( prefetchServerQuery(queryClient, playerQueries.matches(params.playerId));
queryClient, prefetchServerQuery(queryClient, playerQueries.stats(params.playerId));
playerQueries.details(params.playerId) prefetchServerQuery(queryClient, badgeQueries.playerBadges(params.playerId));
),
prefetchServerQuery(
queryClient,
playerQueries.matches(params.playerId)
),
prefetchServerQuery(
queryClient,
playerQueries.stats(params.playerId)
),
prefetchServerQuery(
queryClient,
badgeQueries.playerBadges(params.playerId)
),
]);
}, },
loader: ({ params, context }) => ({ loader: ({ params, context }) => ({
header: { header: {
+2 -2
View File
@@ -12,9 +12,9 @@ const searchSchema = z.object({
export const Route = createFileRoute("/_authed/teams/$teamId")({ export const Route = createFileRoute("/_authed/teams/$teamId")({
validateSearch: searchSchema, validateSearch: searchSchema,
beforeLoad: async ({ params, context }) => { beforeLoad: ({ params, context }) => {
const { queryClient } = context; const { queryClient } = context;
await prefetchServerQuery(queryClient, teamQueries.details(params.teamId)); prefetchServerQuery(queryClient, teamQueries.details(params.teamId));
}, },
loader: ({ params }) => ({ loader: ({ params }) => ({
header: { header: {
+2 -1
View File
@@ -1,6 +1,7 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { msg } from "@lingui/core/macro"; import { msg } from "@lingui/core/macro";
import { superTokensRequestMiddleware } from "@/utils/supertokens"; import { superTokensRequestMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { apiLoggingMiddleware } from "@/lib/telemetry/api-logging.server"; import { apiLoggingMiddleware } from "@/lib/telemetry/api-logging.server";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { logger } from "@/lib/logger"; import { logger } from "@/lib/logger";
@@ -19,7 +20,7 @@ export const Route = createFileRoute("/api/teams/upload-logo")({
const i18n = localizedFor(context.metadata); const i18n = localizedFor(context.metadata);
try { try {
const userId = context.userAuthId; const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin"); const isAdmin = isAdminRole(context.roles);
if (!userId) return new Response("Unauthenticated", { status: 401 }); if (!userId) return new Response("Unauthenticated", { status: 401 });
@@ -1,6 +1,7 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { msg } from "@lingui/core/macro"; import { msg } from "@lingui/core/macro";
import { superTokensRequestMiddleware } from "@/utils/supertokens"; import { superTokensRequestMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { apiLoggingMiddleware } from "@/lib/telemetry/api-logging.server"; import { apiLoggingMiddleware } from "@/lib/telemetry/api-logging.server";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { logger } from "@/lib/logger"; import { logger } from "@/lib/logger";
@@ -19,7 +20,7 @@ export const Route = createFileRoute("/api/tournaments/upload-logo")({
const i18n = localizedFor(context.metadata); const i18n = localizedFor(context.metadata);
try { try {
const userId = context.userAuthId; const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin"); const isAdmin = isAdminRole(context.roles);
if (!userId) return new Response("Unauthenticated", { status: 401 }); if (!userId) return new Response("Unauthenticated", { status: 401 });
if (!isAdmin) return new Response("Unauthorized", { status: 403 }); if (!isAdmin) return new Response("Unauthorized", { status: 403 });
+6
View File
@@ -50,6 +50,12 @@ textarea,
transform: scale(0.90) translateY(-4px); transform: scale(0.90) translateY(-4px);
} }
/* themes paint their full-page texture through this element */
.flxn-theme-overlay {
position: fixed;
pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.app, .app,
[data-drawer-level] { [data-drawer-level] {
+35
View File
@@ -0,0 +1,35 @@
import { Box } from "@mantine/core";
import { FootballHelmetIcon } from "@phosphor-icons/react";
const HeadToHeadIcon = ({ size = 14 }: { size?: number }) => (
<Box
component="span"
style={{
position: "relative",
display: "inline-block",
width: size * (27.5 / 14),
height: size * (16 / 14),
}}
>
<FootballHelmetIcon
size={size}
style={{
position: "absolute",
left: 0,
top: 0,
transform: "rotate(25deg)",
}}
/>
<FootballHelmetIcon
size={size}
style={{
position: "absolute",
right: 0,
top: 0,
transform: "scaleX(-1) rotate(25deg)",
}}
/>
</Box>
);
export default HeadToHeadIcon;
+17
View File
@@ -0,0 +1,17 @@
import { Skeleton, Stack } from "@mantine/core";
const RoutePending = () => (
<Stack gap="md" p="md" w="100%">
{Array.from({ length: 5 }).map((_, index) => (
<Skeleton
key={`route-pending-${index}`}
height={96}
w="100%"
radius="md"
style={{ opacity: Math.max(1 - index * 0.15, 0.3) }}
/>
))}
</Stack>
);
export default RoutePending;
+8 -2
View File
@@ -6,7 +6,7 @@ import {
ReactNode, ReactNode,
useRef, useRef,
useCallback, useCallback,
useMemo, startTransition,
} from "react"; } from "react";
import { useRouter } from "@tanstack/react-router"; import { useRouter } from "@tanstack/react-router";
@@ -48,6 +48,9 @@ function SwipeableTabs({
}, [search?.tab, tabs, defaultTab]); }, [search?.tab, tabs, defaultTab]);
const [activeTab, setActiveTab] = useState(getActiveTabFromUrl); const [activeTab, setActiveTab] = useState(getActiveTabFromUrl);
const [visitedTabs, setVisitedTabs] = useState<ReadonlySet<number>>(
() => new Set([getActiveTabFromUrl()])
);
const [stuck, setStuck] = useState(false); const [stuck, setStuck] = useState(false);
const sentinelRef = useRef<HTMLDivElement>(null); const sentinelRef = useRef<HTMLDivElement>(null);
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null); const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
@@ -60,6 +63,9 @@ function SwipeableTabs({
if (index === activeTab || index < 0 || index >= tabs.length) return; if (index === activeTab || index < 0 || index >= tabs.length) return;
setActiveTab(index); setActiveTab(index);
startTransition(() => {
setVisitedTabs((prev) => (prev.has(index) ? prev : new Set(prev).add(index)));
});
embla?.scrollTo(index); embla?.scrollTo(index);
onTabChange?.(index, tabs[index]); onTabChange?.(index, tabs[index]);
@@ -225,7 +231,7 @@ function SwipeableTabs({
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<Carousel.Slide key={`${tab.label}-content-${index}`}> <Carousel.Slide key={`${tab.label}-content-${index}`}>
<Box ref={setSlideRef(index)} style={{ height: "auto", width: "100%", maxWidth: "100vw", overflow: "hidden" }}> <Box ref={setSlideRef(index)} style={{ height: "auto", width: "100%", maxWidth: "100vw", overflow: "hidden" }}>
{tab.content} {(visitedTabs.has(index) || index === activeTab) ? tab.content : null}
</Box> </Box>
</Carousel.Slide> </Carousel.Slide>
))} ))}
+2 -2
View File
@@ -1,4 +1,4 @@
import { superTokensAdminFunctionMiddleware } from "@/utils/supertokens"; import { superTokensDeveloperFunctionMiddleware } from "@/utils/supertokens";
import { createServerFn } from "@tanstack/react-start"; import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { z } from "zod"; import { z } from "zod";
@@ -17,7 +17,7 @@ const activitySearchParamsSchema = z.object({
export const searchActivities = createServerFn() export const searchActivities = createServerFn()
.validator(activitySearchParamsSchema) .validator(activitySearchParamsSchema)
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<ActivityListResult>(async () => { toServerResult<ActivityListResult>(async () => {
const result = await pbAdmin.searchActivities(data); const result = await pbAdmin.searchActivities(data);
@@ -1,7 +1,6 @@
import { List, Text } from "@mantine/core"; import { List, Text } from "@mantine/core";
import { Plural, Trans, useLingui } from "@lingui/react/macro"; import { Plural, Trans, useLingui } from "@lingui/react/macro";
import { import {
DatabaseIcon,
TreeStructureIcon, TreeStructureIcon,
TrophyIcon, TrophyIcon,
MedalIcon, MedalIcon,
@@ -10,12 +9,14 @@ import {
} from "@phosphor-icons/react"; } from "@phosphor-icons/react";
import ListLink from "@/components/list-link"; import ListLink from "@/components/list-link";
import ListButton from "@/components/list-button"; import ListButton from "@/components/list-button";
import { useIsDeveloper } from "@/features/core/hooks/use-is-developer";
import { getBadgeMigrationStatus, migrateBadgeProgress } from "@/features/badges/server"; import { getBadgeMigrationStatus, migrateBadgeProgress } from "@/features/badges/server";
import { useServerQuery } from "@/lib/tanstack-query/hooks"; import { useServerQuery } from "@/lib/tanstack-query/hooks";
import toast from "@/lib/sonner"; import toast from "@/lib/sonner";
export const ToolsList = () => { export const ToolsList = () => {
const { t } = useLingui(); const { t } = useLingui();
const isDeveloper = useIsDeveloper();
const { data: migrationStatus, refetch: refetchMigrationStatus } = useServerQuery({ const { data: migrationStatus, refetch: refetchMigrationStatus } = useServerQuery({
queryKey: ["admin", "badge-migration-status"], queryKey: ["admin", "badge-migration-status"],
@@ -51,11 +52,13 @@ export const ToolsList = () => {
Icon={TrophyIcon} Icon={TrophyIcon}
to="/admin/tournaments" to="/admin/tournaments"
/> />
<ListLink {isDeveloper && (
label={t`Telemetry`} <ListLink
Icon={PulseIcon} label={t`Telemetry`}
to="/admin/telemetry/usage" Icon={PulseIcon}
/> to="/admin/telemetry/usage"
/>
)}
<ListLink <ListLink
label={t`Award Badges`} label={t`Award Badges`}
Icon={CrownIcon} Icon={CrownIcon}
@@ -91,13 +94,6 @@ export const ToolsList = () => {
<Trans>Badge recalculation failed. Check the server logs.</Trans> <Trans>Badge recalculation failed. Check the server logs.</Trans>
</Text> </Text>
)} )}
<ListButton
label={t`Open Pocketbase`}
Icon={DatabaseIcon}
onClick={() =>
window.location.replace(process.env.POCKETBASE_URL! + "/_/")
}
/>
<ListLink <ListLink
label={t`Bracket Preview`} label={t`Bracket Preview`}
Icon={TreeStructureIcon} Icon={TreeStructureIcon}
@@ -49,6 +49,7 @@ export const BadgeIcon = ({ badge, filled, size = 48 }: BadgeIconProps & { size?
width={size} width={size}
height={size} height={size}
loading="lazy" loading="lazy"
decoding="async"
onError={() => setImageError(true)} onError={() => setImageError(true)}
style={{ style={{
objectFit: 'contain', objectFit: 'contain',
+8 -24
View File
@@ -13,7 +13,7 @@ import {
Tooltip, Tooltip,
} from "@mantine/core"; } from "@mantine/core";
import { AnimatePresence, motion, useReducedMotion } from "framer-motion"; import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
import { FootballHelmetIcon } from "@phosphor-icons/react"; import HeadToHeadIcon from "@/components/head-to-head-icon";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
import { Match } from "@/features/matches/types"; import { Match } from "@/features/matches/types";
import TeamAvatar from "@/components/team-avatar"; import TeamAvatar from "@/components/team-avatar";
@@ -176,35 +176,19 @@ const MatchDock = ({ match, onClose }: MatchDockProps) => {
variant="subtle" variant="subtle"
size="sm" size="sm"
onClick={() => { onClick={() => {
track("h2h_viewed", { source: "match_dock" }); track("h2h_viewed", {
source: "match_dock",
context: [match?.home?.name, match?.away?.name]
.filter(Boolean)
.join(" v "),
});
h2hSheet.open(); h2hSheet.open();
}} }}
aria-label={t`View head-to-head`} aria-label={t`View head-to-head`}
w={40} w={40}
style={{ flexShrink: 0 }} style={{ flexShrink: 0 }}
> >
<Group <HeadToHeadIcon />
style={{ position: "relative", width: 27.5, height: 16 }}
>
<FootballHelmetIcon
size={14}
style={{
position: "absolute",
left: 0,
top: 0,
transform: "rotate(25deg)",
}}
/>
<FootballHelmetIcon
size={14}
style={{
position: "absolute",
right: 0,
top: 0,
transform: "scaleX(-1) rotate(25deg)",
}}
/>
</Group>
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
)} )}
+5 -8
View File
@@ -1,23 +1,22 @@
import { AppShell, Box } from '@mantine/core'; import { AppShell } from '@mantine/core';
import { PropsWithChildren } from 'react'; import { PropsWithChildren } from 'react';
import { useLocation } from '@tanstack/react-router';
import Header from './header'; import Header from './header';
import Navbar from './navbar'; import Navbar from './navbar';
import Pullable from './pullable'; import Pullable from './pullable';
import useVisualViewportSize from '../hooks/use-visual-viewport-size'; import useVisualViewportSize from '../hooks/use-visual-viewport-size';
import useRouterConfig from '../hooks/use-router-config'; import useRouterConfig from '../hooks/use-router-config';
import Page from '@/components/page'; import Page from '@/components/page';
import './route-transition.css';
const Layout: React.FC<PropsWithChildren> = ({ children }) => { const Layout: React.FC<PropsWithChildren> = ({ children }) => {
const { header, withPadding, fullWidth } = useRouterConfig(); const { header, withPadding, fullWidth } = useRouterConfig();
const viewport = useVisualViewportSize(); const viewport = useVisualViewportSize();
const location = useLocation();
return ( return (
<AppShell <AppShell
id='app-shell' id='app-shell'
layout='alt' layout='alt'
// 0 = header collapse is instant; Mantine's default animates main's padding
transitionDuration={0}
header={{ header={{
// Old installs froze black-translucent at add-to-home-screen time, so // Old installs froze black-translucent at add-to-home-screen time, so
// the webview can extend under the status bar; inset is 0 elsewhere. // the webview can extend under the status bar; inset is 0 elsewhere.
@@ -48,16 +47,14 @@ const Layout: React.FC<PropsWithChildren> = ({ children }) => {
pos='relative' pos='relative'
h='100%' h='100%'
mah='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 }} px={{ base: 0.01, sm: 100, md: 200, lg: 300 }}
maw='100dvw' maw='100dvw'
style={{ overflow: 'hidden' }} style={{ overflow: 'hidden' }}
> >
<Pullable> <Pullable>
<Page noPadding={!withPadding} fullWidth={fullWidth}> <Page noPadding={!withPadding} fullWidth={fullWidth}>
<Box key={location.pathname} className="flxn-route-enter"> {children}
{children}
</Box>
</Page> </Page>
</Pullable> </Pullable>
</AppShell.Main> </AppShell.Main>
@@ -0,0 +1,68 @@
import { useLayoutEffect, useRef } from "react";
import { useRouterState } from "@tanstack/react-router";
const TRAVEL_MS = 333;
export const useNavIndicator = (isMobile: boolean) => {
const containerRef = useRef<HTMLDivElement>(null);
const indicatorRef = useRef<HTMLSpanElement>(null);
const shapeRef = useRef<HTMLSpanElement>(null);
const pathname = useRouterState({ select: (s) => s.location.pathname });
useLayoutEffect(() => {
const container = containerRef.current;
const indicator = indicatorRef.current;
const shape = shapeRef.current;
if (!container || !indicator || !shape) return;
let last: number | null = null;
const place = () => {
const active = container.querySelector<HTMLElement>("[data-active]");
if (!active) {
delete indicator.dataset.placed;
last = null;
return;
}
const x = active.offsetLeft;
const y = active.offsetTop;
const travelled = isMobile ? x : y;
const first = indicator.dataset.placed !== "1";
if (first) indicator.style.transition = "none";
indicator.style.setProperty("--nav-x", `${x}px`);
indicator.style.setProperty("--nav-y", `${y}px`);
indicator.style.setProperty("--nav-w", `${active.offsetWidth}px`);
indicator.style.setProperty("--nav-h", `${active.offsetHeight}px`);
indicator.dataset.placed = "1";
if (first) {
indicator.getBoundingClientRect();
indicator.style.transition = "";
} else if (
last !== null &&
Math.abs(travelled - last) > 1 &&
!window.matchMedia("(prefers-reduced-motion: reduce)").matches
) {
const axis = isMobile ? "scaleX" : "scaleY";
shape.animate(
[
{ transform: `${axis}(1)`, offset: 0 },
{ transform: `${axis}(0.88)`, offset: 0.4 },
{ transform: `${axis}(1)`, offset: 1 },
],
{ duration: TRAVEL_MS, easing: "ease-in-out" }
);
}
last = travelled;
};
place();
const observer = new ResizeObserver(place);
observer.observe(container);
return () => observer.disconnect();
}, [pathname, isMobile]);
return { containerRef, indicatorRef, shapeRef };
};
@@ -32,30 +32,19 @@ export const NavLink = ({
<Box <Box
component={Link} component={Link}
to={href} to={href}
className={styles.navLinkBox} className={`flxn-nav-link ${styles.navLinkBox}`}
p={{ base: 0, sm: 8 }} p={{ base: 0, sm: 8 }}
aria-label={label} aria-label={label}
aria-current={isActive ? "page" : undefined} aria-current={isActive ? "page" : undefined}
data-active={isActive || undefined}
> >
<Flex <Flex
direction={{ base: "column", md: "row" }} direction={{ base: "column", md: "row" }}
align="center" align="center"
gap={{ base: 0, md: "xs" }} gap={{ base: 0, md: "xs" }}
> >
<Icon <Icon weight={isActive ? "fill" : "regular"} size={28} />
weight={isActive ? "fill" : "regular"} <Text visibleFrom="md" ta="center" size="md" fw={isActive ? 800 : 500}>
size={28}
style={{
color: isActive ? "var(--mantine-primary-color-filled)" : undefined,
}}
/>
<Text
visibleFrom="md"
ta="center"
size="md"
fw={isActive ? 800 : 500}
c={isActive ? "var(--mantine-primary-color-filled)" : undefined}
>
{label} {label}
</Text> </Text>
</Flex> </Flex>
@@ -11,11 +11,6 @@
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
.navLinkBox svg,
.navLinkBox p {
transition: color 150ms ease-out;
}
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
.navLinkBox { .navLinkBox {
transition: transform 150ms cubic-bezier(0.32, 0.72, 0, 1); transition: transform 150ms cubic-bezier(0.32, 0.72, 0, 1);
+14 -4
View File
@@ -5,11 +5,13 @@ import { useAuth } from "@/contexts/auth-context";
import { useLinks } from "../hooks/use-links"; import { useLinks } from "../hooks/use-links";
import { memo } from "react"; import { memo } from "react";
import { useIsPWA } from "@/hooks/use-is-pwa"; import { useIsPWA } from "@/hooks/use-is-pwa";
import { useNavIndicator } from "./nav-indicator/use-nav-indicator";
const Navbar = () => { const Navbar = () => {
const { user, roles } = useAuth() const { user, roles } = useAuth()
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const isPWA = useIsPWA(); const isPWA = useIsPWA();
const { containerRef, indicatorRef, shapeRef } = useNavIndicator(isMobile);
const links = useLinks(user?.id, roles); const links = useLinks(user?.id, roles);
@@ -17,9 +19,16 @@ const Navbar = () => {
? 'max(env(safe-area-inset-bottom, 0px), 0.5rem)' ? 'max(env(safe-area-inset-bottom, 0px), 0.5rem)'
: 'env(safe-area-inset-bottom, 0px)'; : 'env(safe-area-inset-bottom, 0px)';
const indicator = (
<span ref={indicatorRef} className="flxn-nav-indicator" aria-hidden="true">
<span ref={shapeRef} className="flxn-nav-indicator-shape" />
</span>
);
if (isMobile) return ( if (isMobile) return (
<Paper component='nav' role='navigation' withBorder shadow="sm" radius='lg' h='4rem' w='calc(100% - 1rem)' pos='fixed' m='0.5rem' bottom={bottomOffset} style={{ zIndex: 10 }}> <Paper className="flxn-nav" component='nav' role='navigation' withBorder shadow="sm" radius='lg' h='4rem' w='calc(100% - 1rem)' pos='fixed' m='0.5rem' bottom={bottomOffset} style={{ zIndex: 10 }}>
<Group gap='xs' justify='space-around' h='100%' w='100%' px={{ base: 12, sm: 0 }}> <Group ref={containerRef} pos='relative' gap='xs' justify='space-around' h='100%' w='100%' px={{ base: 12, sm: 0 }}>
{indicator}
{links.map((link) => ( {links.map((link) => (
<NavLink key={link.href} {...link} /> <NavLink key={link.href} {...link} />
))} ))}
@@ -27,9 +36,10 @@ const Navbar = () => {
</Paper> </Paper>
) )
return <AppShell.Navbar p="xs" role='navigation'> return <AppShell.Navbar className="flxn-nav" p="xs" role='navigation'>
<AppShell.Section grow component={ScrollArea}> <AppShell.Section grow component={ScrollArea}>
<Stack gap='xs' mx='auto' w='fit-content' justify='end' mt='md'> <Stack ref={containerRef} pos='relative' gap='xs' mx='auto' w='fit-content' justify='end' mt='md'>
{indicator}
{links.map((link) => ( {links.map((link) => (
<NavLink key={link.href} {...link} /> <NavLink key={link.href} {...link} />
))} ))}
@@ -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;
}
}
@@ -0,0 +1,7 @@
import { useAuth } from "@/contexts/auth-context";
import { isDeveloperRole } from "../utils/roles";
export const useIsDeveloper = (): boolean => {
const { roles } = useAuth();
return isDeveloperRole(roles);
};
+5 -1
View File
@@ -1,4 +1,8 @@
export const ADMIN_ROLE = "Admin"; export const ADMIN_ROLE = "Admin";
export const DEVELOPER_ROLE = "Developer";
export const isDeveloperRole = (roles?: string[] | null): boolean =>
!!roles?.includes(DEVELOPER_ROLE);
export const isAdminRole = (roles?: string[] | null): boolean => export const isAdminRole = (roles?: string[] | null): boolean =>
!!roles?.includes(ADMIN_ROLE); !!roles?.includes(ADMIN_ROLE) || isDeveloperRole(roles);
+1 -1
View File
@@ -21,7 +21,7 @@ const useConsumeCode = (onWrongCode: () => void) => {
} else { } else {
queryClient.setQueryData(playerKeys.auth, response.data); queryClient.setQueryData(playerKeys.auth, response.data);
toast.success(t`Successfully logged in. Welcome back!`); toast.success(t`Successfully logged in. Welcome back!`);
navigate({ to: '/' }) window.location.href = '/';
} }
} else if (data.status === 'INCORRECT_USER_INPUT_CODE_ERROR') { } else if (data.status === 'INCORRECT_USER_INPUT_CODE_ERROR') {
onWrongCode(); onWrongCode();
+1 -1
View File
@@ -21,7 +21,7 @@ const useCreateUser = () => {
...old, ...old,
user: data user: data
})); }));
navigate({ to: '/' }); window.location.href = '/';
} }
}); });
}; };
+6 -22
View File
@@ -1,5 +1,5 @@
import { Text, Group, Stack, Paper, Indicator, Box, Tooltip, ActionIcon } from "@mantine/core"; import { Text, Group, Stack, Paper, Indicator, Box, Tooltip, ActionIcon } from "@mantine/core";
import { FootballHelmetIcon } from "@phosphor-icons/react"; import HeadToHeadIcon from "@/components/head-to-head-icon";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
import { Match } from "../types"; import { Match } from "../types";
import TeamAvatar from "@/components/team-avatar"; import TeamAvatar from "@/components/team-avatar";
@@ -42,7 +42,10 @@ const MatchCard = ({ match, hideH2H = false }: MatchCardProps) => {
const handleH2HClick = (e: React.MouseEvent) => { const handleH2HClick = (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
track("h2h_viewed", { source: "match_card" }); track("h2h_viewed", {
source: "match_card",
context: [match.home?.name, match.away?.name].filter(Boolean).join(" v "),
});
h2hSheet.open(); h2hSheet.open();
}; };
@@ -91,26 +94,7 @@ const MatchCard = ({ match, hideH2H = false }: MatchCardProps) => {
aria-label={t`View head-to-head`} aria-label={t`View head-to-head`}
w={40} w={40}
> >
<Group style={{ position: 'relative', width: 27.5, height: 16 }}> <HeadToHeadIcon />
<FootballHelmetIcon
size={14}
style={{
position: 'absolute',
left: 0,
top: 0,
transform: 'rotate(25deg)',
}}
/>
<FootballHelmetIcon
size={14}
style={{
position: 'absolute',
right: 0,
top: 0,
transform: 'scaleX(-1) rotate(25deg)',
}}
/>
</Group>
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
)} )}
@@ -1,4 +1,4 @@
import { Stack, Text } from "@mantine/core"; import { MantineSpacing, Stack, Text } from "@mantine/core";
import { useMemo } from "react"; import { useMemo } from "react";
import { Match } from "../types"; import { Match } from "../types";
import MatchCard from "./match-card"; import MatchCard from "./match-card";
@@ -7,9 +7,10 @@ import { Trans } from "@lingui/react/macro";
interface MatchListProps { interface MatchListProps {
matches: Match[]; matches: Match[];
hideH2H?: boolean; hideH2H?: boolean;
p?: MantineSpacing;
} }
const MatchList = ({ matches, hideH2H = false }: MatchListProps) => { const MatchList = ({ matches, hideH2H = false, p = "md" }: MatchListProps) => {
const filteredMatches = useMemo( const filteredMatches = useMemo(
() => () =>
[...(matches ?? [])] [...(matches ?? [])]
@@ -27,7 +28,7 @@ const MatchList = ({ matches, hideH2H = false }: MatchListProps) => {
const isRegional = filteredMatches[0]?.tournament?.regional; const isRegional = filteredMatches[0]?.tournament?.regional;
return ( return (
<Stack p="md" gap="sm"> <Stack p={p} gap="sm">
{isRegional && ( {isRegional && (
<Text size="xs" c="dimmed" ta="center" px="md"> <Text size="xs" c="dimmed" ta="center" px="md">
<Trans>Matches for regionals are unordered</Trans> <Trans>Matches for regionals are unordered</Trans>
@@ -200,10 +200,10 @@ const TeamHeadToHeadContent = ({ team1, team2, isOpen = true }: TeamHeadToHeadSh
</Stack> </Stack>
<Stack gap="xs"> <Stack gap="xs">
<Text size="sm" fw={600} px="md"> <Text size="sm" fw={600}>
<Trans>Match History (<Plural value={totalMatches} one="# match" other="# matches" />)</Trans> <Trans>Match History (<Plural value={totalMatches} one="# match" other="# matches" />)</Trans>
</Text> </Text>
<MatchList matches={matches} hideH2H /> <MatchList matches={matches} hideH2H p={0} />
</Stack> </Stack>
</Stack> </Stack>
); );
@@ -53,7 +53,12 @@ const LeagueHeadToHead = () => {
setPlayer1Id(playerId); setPlayer1Id(playerId);
} else { } else {
setPlayer2Id(playerId); setPlayer2Id(playerId);
track("h2h_viewed", { source: "stats" }); const opponent =
allPlayerStats?.find((p) => p.player_id === playerId)?.player_name || "";
track("h2h_viewed", {
source: "stats",
context: [player1Name, opponent].filter(Boolean).join(" v "),
});
h2hSheet.open(); h2hSheet.open();
} }
}; };
@@ -260,10 +260,10 @@ const PlayerHeadToHeadContent = ({
</Stack> </Stack>
<Stack gap="xs"> <Stack gap="xs">
<Text size="sm" fw={600} px="md"> <Text size="sm" fw={600}>
<Trans>Match History ({totalMatches})</Trans> <Trans>Match History ({totalMatches})</Trans>
</Text> </Text>
<MatchList matches={matches} hideH2H /> <MatchList matches={matches} hideH2H p={0} />
</Stack> </Stack>
</Stack> </Stack>
); );
@@ -293,11 +293,10 @@ const PlayerStatsTable = ({ viewType = 'all' }: PlayerStatsTableProps) => {
}, [navigate]); }, [navigate]);
const handleSort = (key: SortKey) => { const handleSort = (key: SortKey) => {
track("stats_sorted", { value: key }); const direction =
setSortConfig((prev) => ({ sortConfig.key === key && sortConfig.direction === "desc" ? "asc" : "desc";
key, track("stats_sorted", { value: key, context: direction });
direction: prev.key === key && prev.direction === "desc" ? "asc" : "desc", setSortConfig({ key, direction });
}));
}; };
const getSortIcon = (key: SortKey) => { const getSortIcon = (key: SortKey) => {
@@ -1,7 +1,8 @@
import Sheet from "@/components/sheet/sheet"; import Sheet from "@/components/sheet/sheet";
import { useAuth } from "@/contexts/auth-context"; import { useAuth } from "@/contexts/auth-context";
import { Flex, Title, ActionIcon, Stack, Button, Box } from "@mantine/core"; import { Flex, Title, ActionIcon, Stack, Button } from "@mantine/core";
import { PencilIcon, FootballHelmetIcon } from "@phosphor-icons/react"; import { PencilIcon } from "@phosphor-icons/react";
import HeadToHeadIcon from "@/components/head-to-head-icon";
import { useMemo } from "react"; import { useMemo } from "react";
import { useLingui } from "@lingui/react/macro"; import { useLingui } from "@lingui/react/macro";
import NameUpdateForm from "./name-form"; import NameUpdateForm from "./name-form";
@@ -55,32 +56,16 @@ const Header = ({ player }: HeaderProps) => {
size="sm" size="sm"
radius="xl" radius="xl"
onClick={() => { onClick={() => {
track("h2h_viewed", { source: "profile" }); track("h2h_viewed", {
source: "profile",
context: [player.first_name, player.last_name].filter(Boolean).join(" "),
});
h2hSheet.open(); h2hSheet.open();
}} }}
w={40} w={40}
display={!owner ? 'block' : 'none'} display={!owner ? 'block' : 'none'}
> >
<Box style={{ position: 'relative', width: 27.5, height: 16 }}> <HeadToHeadIcon />
<FootballHelmetIcon
size={14}
style={{
position: 'absolute',
left: 0,
top: 0,
transform: 'rotate(25deg)'
}}
/>
<FootballHelmetIcon
size={14}
style={{
position: 'absolute',
right: 0,
top: 0,
transform: 'scaleX(-1) rotate(25deg)'
}}
/>
</Box>
</ActionIcon> </ActionIcon>
</Flex> </Flex>
</Flex> </Flex>
@@ -7,7 +7,8 @@ import {
Stack, Stack,
Text, Text,
} from "@mantine/core"; } from "@mantine/core";
import { CaretLeftIcon, CaretRightIcon, InfoIcon } from "@phosphor-icons/react"; import { CaretLeftIcon, CaretRightIcon } from "@phosphor-icons/react";
import HeadToHeadIcon from "@/components/head-to-head-icon";
import WizardOrbIcon from "@/components/wizard-orb-icon"; import WizardOrbIcon from "@/components/wizard-orb-icon";
import { Trans, useLingui } from "@lingui/react/macro"; import { Trans, useLingui } from "@lingui/react/macro";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
@@ -218,14 +219,17 @@ export const PredictionEditor: React.FC<PredictionEditorProps> = ({
radius="md" radius="md"
onClick={() => { onClick={() => {
if (activeResolved?.home && activeResolved?.away) { if (activeResolved?.home && activeResolved?.away) {
track("h2h_viewed", { source: "predictions" }); track("h2h_viewed", {
source: "predictions",
context: `${activeResolved.home.name} v ${activeResolved.away.name}`,
});
} }
matchupSheet.open(); matchupSheet.open();
}} }}
disabled={!activeResolved?.home && !activeResolved?.away} disabled={!activeResolved?.home && !activeResolved?.away}
aria-label={t`Matchup details`} aria-label={t`Matchup details`}
> >
<InfoIcon size={16} /> <HeadToHeadIcon />
</ActionIcon> </ActionIcon>
</Group> </Group>
<Group gap="sm" align="center" wrap="nowrap"> <Group gap="sm" align="center" wrap="nowrap">
@@ -3,7 +3,7 @@ import { Box, Title, Stack, Switch, Button, Text, Group } from "@mantine/core";
import { Trans, useLingui } from "@lingui/react/macro"; import { Trans, useLingui } from "@lingui/react/macro";
import { useWebPush } from "@/hooks/use-web-push"; import { useWebPush } from "@/hooks/use-web-push";
import { useAuth } from "@/contexts/auth-context"; import { useAuth } from "@/contexts/auth-context";
import { useIsAdmin } from "@/features/core/hooks/use-is-admin"; import { useIsDeveloper } from "@/features/core/hooks/use-is-developer";
import { updateUserTelemetryAlerts } from "@/features/settings/server"; import { updateUserTelemetryAlerts } from "@/features/settings/server";
import toast from "@/lib/sonner"; import toast from "@/lib/sonner";
@@ -40,7 +40,7 @@ const TelemetryAlertsSwitch = () => {
export function NotificationsSection() { export function NotificationsSection() {
const { supported, configured, permission, subscribed, busy, enable, disable, sendTest } = const { supported, configured, permission, subscribed, busy, enable, disable, sendTest } =
useWebPush(); useWebPush();
const isAdmin = useIsAdmin(); const isDeveloper = useIsDeveloper();
const { t } = useLingui(); const { t } = useLingui();
if (!supported || !configured) { if (!supported || !configured) {
@@ -99,7 +99,7 @@ export function NotificationsSection() {
disabled={busy} disabled={busy}
label={t`Enable notifications on this device`} label={t`Enable notifications on this device`}
/> />
{subscribed && isAdmin && <TelemetryAlertsSwitch />} {subscribed && isDeveloper && <TelemetryAlertsSwitch />}
{subscribed && ( {subscribed && (
<Group> <Group>
<Button <Button
+2 -2
View File
@@ -1,6 +1,6 @@
import { createServerFn } from "@tanstack/react-start"; import { createServerFn } from "@tanstack/react-start";
import { import {
superTokensAdminFunctionMiddleware, superTokensDeveloperFunctionMiddleware,
superTokensFunctionMiddleware, superTokensFunctionMiddleware,
} from "@/utils/supertokens"; } from "@/utils/supertokens";
@@ -145,7 +145,7 @@ export const updateUserAccentColor = createServerFn({ method: "POST" })
export const updateUserTelemetryAlerts = createServerFn({ method: "POST" }) export const updateUserTelemetryAlerts = createServerFn({ method: "POST" })
.validator((data: boolean) => data === true) .validator((data: boolean) => data === true)
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ context, data }) => { .handler(async ({ context, data }) => {
const { userAuthId, metadata } = context; const { userAuthId, metadata } = context;
if (!userAuthId) return; if (!userAuthId) return;
+3 -2
View File
@@ -1,4 +1,5 @@
import { superTokensFunctionMiddleware, superTokensAdminFunctionMiddleware } from "@/utils/supertokens"; import { superTokensFunctionMiddleware, superTokensAdminFunctionMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { createServerFn } from "@tanstack/react-start"; import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { z } from "zod"; import { z } from "zod";
@@ -35,7 +36,7 @@ export const createTeam = createServerFn()
.handler(async ({ data, context }) => .handler(async ({ data, context }) =>
toServerResult(async () => { toServerResult(async () => {
const userId = context.userAuthId; const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin"); const isAdmin = isAdminRole(context.roles);
//if (!isAdmin && !data.players.includes(userId)) { //if (!isAdmin && !data.players.includes(userId)) {
// throw new Error("You can only create teams that include yourself as a player"); // throw new Error("You can only create teams that include yourself as a player");
@@ -57,7 +58,7 @@ export const updateTeam = createServerFn()
.handler(async ({ data: { id, updates }, context }) => .handler(async ({ data: { id, updates }, context }) =>
toServerResult(async () => { toServerResult(async () => {
const userId = context.userAuthId; const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin"); const isAdmin = isAdminRole(context.roles);
const team = await pbAdmin.getTeam(id); const team = await pbAdmin.getTeam(id);
if (!team) { if (!team) {
+66 -49
View File
@@ -1,6 +1,6 @@
import { createServerFn } from "@tanstack/react-start"; import { createServerFn } from "@tanstack/react-start";
import { z } from "zod"; import { z } from "zod";
import { superTokensAdminFunctionMiddleware } from "@/utils/supertokens"; import { superTokensDeveloperFunctionMiddleware } from "@/utils/supertokens";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result"; import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
import { import {
@@ -37,7 +37,7 @@ const clientEventSearchSchema = z.object({
export const searchClientEvents = createServerFn() export const searchClientEvents = createServerFn()
.validator(clientEventSearchSchema) .validator(clientEventSearchSchema)
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<TelemetryListResult<ClientEvent>>(async () => { toServerResult<TelemetryListResult<ClientEvent>>(async () => {
const result = await pbAdmin.searchClientEvents(data); const result = await pbAdmin.searchClientEvents(data);
@@ -58,7 +58,7 @@ const clientErrorSearchSchema = z.object({
export const searchClientErrors = createServerFn() export const searchClientErrors = createServerFn()
.validator(clientErrorSearchSchema) .validator(clientErrorSearchSchema)
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<TelemetryListResult<ClientError>>(async () => { toServerResult<TelemetryListResult<ClientError>>(async () => {
const result = await pbAdmin.searchClientErrors(data); const result = await pbAdmin.searchClientErrors(data);
@@ -73,59 +73,75 @@ const errorGroupsSchema = z.object({
limit: z.number().max(100).optional(), limit: z.number().max(100).optional(),
}); });
// Aggregates raw rows, not rollups: a brand-new error must appear immediately,
// not on the next rollup tick.
export const getClientErrorGroups = createServerFn() export const getClientErrorGroups = createServerFn()
.validator(errorGroupsSchema) .validator(errorGroupsSchema)
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<ClientErrorGroup[]>(async () => { toServerResult<ClientErrorGroup[]>(async () => {
const { from, to, includeResolved = false, limit = 50 } = data; const { from, to, includeResolved = false, limit = 50 } = data;
const rollups = await pbAdmin.getRollupRange({ const toExclusive = new Date(`${to}T00:00:00.000Z`);
metrics: ["client_error.count"], toExclusive.setUTCDate(toExclusive.getUTCDate() + 1);
from, const filter = `created >= '${from} 00:00:00.000Z' && created < '${toExclusive
to, .toISOString()
}); .replace("T", " ")}'`;
const countByGroup = new Map<string, number>(); const byGroup = new Map<string, ClientErrorGroup>();
for (const rollup of rollups) { await pbAdmin.pageCollection<{
if (!rollup.dim) continue; group_hash: string;
countByGroup.set(rollup.dim, (countByGroup.get(rollup.dim) ?? 0) + rollup.value); message: string;
} stack?: string;
source?: string;
path?: string;
resolved?: boolean;
created: string;
}>(
"client_errors",
{ filter, fields: "group_hash,message,stack,source,path,resolved,created" },
(rows) => {
for (const row of rows) {
const existing = byGroup.get(row.group_hash);
if (existing) {
existing.count++;
// Paged in created-asc order, so each row is the newest so far.
existing.lastSeen = row.created;
existing.resolved = !!row.resolved;
existing.sample = {
message: row.message,
stack: row.stack,
path: row.path,
source: row.source,
};
} else {
byGroup.set(row.group_hash, {
group_hash: row.group_hash,
count: 1,
lastSeen: row.created,
resolved: !!row.resolved,
sample: {
message: row.message,
stack: row.stack,
path: row.path,
source: row.source,
},
});
}
}
}
);
const topGroups = [...countByGroup.entries()] return [...byGroup.values()]
.sort((a, b) => b[1] - a[1]) .filter((group) => includeResolved || !group.resolved)
.sort((a, b) => b.count - a.count)
.slice(0, limit); .slice(0, limit);
const groups: ClientErrorGroup[] = [];
for (const [groupHash, count] of topGroups) {
const latest = await pbAdmin.searchClientErrors({ groupHash, perPage: 1 });
const sample = latest.items[0];
if (!sample) continue;
const resolved = !!sample.resolved;
if (resolved && !includeResolved) continue;
groups.push({
group_hash: groupHash,
count,
lastSeen: sample.created,
resolved,
sample: {
message: sample.message,
stack: sample.stack,
path: sample.path,
source: sample.source,
},
});
}
return groups;
}) })
); );
export const resolveClientErrorGroup = createServerFn({ method: "POST" }) export const resolveClientErrorGroup = createServerFn({ method: "POST" })
.validator(z.object({ groupHash: z.string(), resolved: z.boolean().optional() })) .validator(z.object({ groupHash: z.string(), resolved: z.boolean().optional() }))
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<{ updated: number }>(async () => { toServerResult<{ updated: number }>(async () => {
const updated = await pbAdmin.resolveErrorsByGroup(data.groupHash, data.resolved ?? true); const updated = await pbAdmin.resolveErrorsByGroup(data.groupHash, data.resolved ?? true);
@@ -142,7 +158,7 @@ const rollupRangeSchema = z.object({
export const getRollupRange = createServerFn() export const getRollupRange = createServerFn()
.validator(rollupRangeSchema) .validator(rollupRangeSchema)
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<RollupRow[]>(async () => pbAdmin.getRollupRange(data)) toServerResult<RollupRow[]>(async () => pbAdmin.getRollupRange(data))
); );
@@ -151,7 +167,7 @@ export const listTelemetryAlerts = createServerFn()
.validator( .validator(
z.object({ page: z.number().optional(), perPage: z.number().max(200).optional() }) z.object({ page: z.number().optional(), perPage: z.number().max(200).optional() })
) )
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<TelemetryListResult<TelemetryAlert>>(async () => toServerResult<TelemetryListResult<TelemetryAlert>>(async () =>
pbAdmin.listTelemetryAlerts(data.page, data.perPage) pbAdmin.listTelemetryAlerts(data.page, data.perPage)
@@ -181,7 +197,7 @@ export const listSessions = createServerFn()
perPage: z.number().max(100).optional(), perPage: z.number().max(100).optional(),
}) })
) )
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<TelemetryListResult<SessionSummary>>(async () => { toServerResult<TelemetryListResult<SessionSummary>>(async () => {
const { days = 7, page = 1, perPage = 30 } = data; const { days = 7, page = 1, perPage = 30 } = data;
@@ -269,7 +285,7 @@ const MAX_TIMELINE_ENTRIES = 500;
export const getSessionTimeline = createServerFn() export const getSessionTimeline = createServerFn()
.validator(z.object({ sessionId: z.string().min(8).max(64) })) .validator(z.object({ sessionId: z.string().min(8).max(64) }))
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async ({ data }) => .handler(async ({ data }) =>
toServerResult<SessionTimeline>(async () => { toServerResult<SessionTimeline>(async () => {
const events = await pbAdmin.searchClientEvents({ const events = await pbAdmin.searchClientEvents({
@@ -303,16 +319,17 @@ export const getSessionTimeline = createServerFn()
for (const event of events.items) { for (const event of events.items) {
if (event.name.startsWith("vital.")) continue; if (event.name.startsWith("vital.")) continue;
const props = event.props as const props = event.props as
| { emoji?: string; source?: string; tournament?: string; value?: string } | { emoji?: string; source?: string; tournament?: string; value?: string; context?: string }
| null | null
| undefined; | undefined;
const breakdown = const breakdown =
props?.emoji ?? props?.value ?? props?.source ?? props?.tournament; props?.emoji ?? props?.value ?? props?.source ?? props?.tournament;
const detail = [breakdown, props?.context].filter(Boolean).join(" · ");
entries.push({ entries.push({
ts: event.created, ts: event.created,
kind: event.name === "page_view" ? "page_view" : "event", kind: event.name === "page_view" ? "page_view" : "event",
label: event.name === "page_view" ? event.path || event.route_id || "?" : event.name, label: event.name === "page_view" ? event.path || event.route_id || "?" : event.name,
detail: event.name === "page_view" ? undefined : (breakdown ?? event.path), detail: event.name === "page_view" ? undefined : (detail || event.path),
}); });
} }
@@ -409,7 +426,7 @@ export const getSessionTimeline = createServerFn()
); );
export const getTelemetryRuntimeStatus = createServerFn() export const getTelemetryRuntimeStatus = createServerFn()
.middleware([superTokensAdminFunctionMiddleware]) .middleware([superTokensDeveloperFunctionMiddleware])
.handler(async () => .handler(async () =>
toServerResult<TelemetryRuntimeStatus>(async () => { toServerResult<TelemetryRuntimeStatus>(async () => {
ensureTelemetryScheduler(); ensureTelemetryScheduler();
@@ -2,11 +2,15 @@ import { Group, Stack, ThemeIcon, Text } from "@mantine/core";
import { Tournament } from "../../types"; import { Tournament } from "../../types";
import { CalendarIcon, MapPinIcon, TrophyIcon } from "@phosphor-icons/react"; import { CalendarIcon, MapPinIcon, TrophyIcon } from "@phosphor-icons/react";
import { useMemo } from "react"; import { useMemo } from "react";
import { useMounted } from "@mantine/hooks";
import { Trans, useLingui } from "@lingui/react/macro"; import { Trans, useLingui } from "@lingui/react/macro";
import GlitchAvatar from "@/components/glitch-avatar"; import GlitchAvatar from "@/components/glitch-avatar";
const Header = ({ tournament }: { tournament: Tournament }) => { const Header = ({ tournament }: { tournament: Tournament }) => {
const { i18n } = useLingui(); const { i18n } = useLingui();
// Local-timezone text cannot be server-rendered: the pod formats in UTC and
// hydration mismatches on every load.
const mounted = useMounted();
const tournamentStart = useMemo( const tournamentStart = useMemo(
() => new Date(tournament.start_time), () => new Date(tournament.start_time),
[tournament.start_time] [tournament.start_time]
@@ -48,18 +52,20 @@ const Header = ({ tournament }: { tournament: Tournament }) => {
<CalendarIcon size={14} /> <CalendarIcon size={14} />
</ThemeIcon> </ThemeIcon>
<Text size="sm" c="dimmed" lineClamp={1}> <Text size="sm" c="dimmed" lineClamp={1}>
<Trans> {mounted && (
{tournamentStart.toLocaleDateString(i18n.locale, { <Trans>
weekday: "short", {tournamentStart.toLocaleDateString(i18n.locale, {
month: "short", weekday: "short",
day: "numeric", month: "short",
})}{" "} day: "numeric",
at{" "} })}{" "}
{tournamentStart.toLocaleTimeString(i18n.locale, { at{" "}
hour: "2-digit", {tournamentStart.toLocaleTimeString(i18n.locale, {
minute: "2-digit", hour: "2-digit",
})} minute: "2-digit",
</Trans> })}
</Trans>
)}
</Text> </Text>
</Group> </Group>
</Stack> </Stack>
@@ -5,6 +5,7 @@ import { TournamentInfo } from "../types";
import { Fragment, useCallback } from "react"; import { Fragment, useCallback } from "react";
import React from "react"; import React from "react";
import { TrophyIcon, CalendarIcon, MapPinIcon } from "@phosphor-icons/react"; import { TrophyIcon, CalendarIcon, MapPinIcon } from "@phosphor-icons/react";
import { useMounted } from "@mantine/hooks";
import { Trans, useLingui } from "@lingui/react/macro"; import { Trans, useLingui } from "@lingui/react/macro";
interface TournamentListProps { interface TournamentListProps {
@@ -18,6 +19,7 @@ interface TournamentListItemProps {
const TournamentListItem = React.memo(({ tournament }: TournamentListItemProps) => { const TournamentListItem = React.memo(({ tournament }: TournamentListItemProps) => {
const { i18n } = useLingui(); const { i18n } = useLingui();
const mounted = useMounted();
const startDate = tournament.start_time ? new Date(tournament.start_time) : null; const startDate = tournament.start_time ? new Date(tournament.start_time) : null;
return ( return (
@@ -43,11 +45,12 @@ const TournamentListItem = React.memo(({ tournament }: TournamentListItemProps)
<CalendarIcon size={10} /> <CalendarIcon size={10} />
</ThemeIcon> </ThemeIcon>
<Text size="xs" c="dimmed"> <Text size="xs" c="dimmed">
{startDate.toLocaleDateString(i18n.locale, { {mounted &&
month: 'short', startDate.toLocaleDateString(i18n.locale, {
day: 'numeric', month: 'short',
year: 'numeric' day: 'numeric',
})} year: 'numeric'
})}
</Text> </Text>
</Group> </Group>
)} )}
+3 -2
View File
@@ -1,4 +1,5 @@
import { superTokensAdminFunctionMiddleware, superTokensFunctionMiddleware } from "@/utils/supertokens"; import { superTokensAdminFunctionMiddleware, superTokensFunctionMiddleware } from "@/utils/supertokens";
import { isAdminRole } from "@/features/core/utils/roles";
import { createServerFn } from "@tanstack/react-start"; import { createServerFn } from "@tanstack/react-start";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { tournamentInputSchema, GroupStanding } from "@/features/tournaments/types"; import { tournamentInputSchema, GroupStanding } from "@/features/tournaments/types";
@@ -46,7 +47,7 @@ export const getTournament = createServerFn()
.validator(z.string()) .validator(z.string())
.middleware([superTokensFunctionMiddleware]) .middleware([superTokensFunctionMiddleware])
.handler(async ({ data: tournamentId, context }) => { .handler(async ({ data: tournamentId, context }) => {
const isAdmin = context.roles.includes("Admin"); const isAdmin = isAdminRole(context.roles);
return toServerResult(() => pbAdmin.getTournament(tournamentId, isAdmin)); return toServerResult(() => pbAdmin.getTournament(tournamentId, isAdmin));
}); });
@@ -64,7 +65,7 @@ export const enrollTeam = createServerFn()
.handler(async ({ data: { tournamentId, teamId }, context }) => .handler(async ({ data: { tournamentId, teamId }, context }) =>
toServerResult(async () => { toServerResult(async () => {
const userId = context.userAuthId; const userId = context.userAuthId;
const isAdmin = context.roles.includes("Admin"); const isAdmin = isAdminRole(context.roles);
const team = await pbAdmin.getTeam(teamId); const team = await pbAdmin.getTeam(teamId);
if (!team) { throw new Error('Team not found'); } if (!team) { throw new Error('Team not found'); }
+10
View File
@@ -1,6 +1,16 @@
import { QueryClient, QueryKey } from "@tanstack/react-query"; import { QueryClient, QueryKey } from "@tanstack/react-query";
import { ServerResult } from "../types"; import { ServerResult } from "../types";
export function getServerQueryData<TData>(
queryClient: QueryClient,
query: {
queryKey: QueryKey;
queryFn: () => Promise<ServerResult<TData>>;
}
): TData | undefined {
return queryClient.getQueryData<TData>(query.queryKey);
}
export async function ensureServerQueryData<TData>( export async function ensureServerQueryData<TData>(
queryClient: QueryClient, queryClient: QueryClient,
query: { query: {
+11 -11
View File
@@ -2,7 +2,7 @@ import { msg } from "@lingui/core/macro";
import { pbAdmin } from "@/lib/pocketbase/client"; import { pbAdmin } from "@/lib/pocketbase/client";
import { sendPushToPlayer } from "@/lib/push"; import { sendPushToPlayer } from "@/lib/push";
import { Logger } from "@/lib/logger"; import { Logger } from "@/lib/logger";
import { ADMIN_ROLE } from "@/features/core/utils/roles"; import { DEVELOPER_ROLE } from "@/features/core/utils/roles";
const logger = new Logger("Telemetry > Alerts"); const logger = new Logger("Telemetry > Alerts");
@@ -22,23 +22,23 @@ const fnFailureThreshold = (): number => {
const pbTimestamp = (date: Date): string => date.toISOString().replace("T", " "); const pbTimestamp = (date: Date): string => date.toISOString().replace("T", " ");
const ADMIN_CACHE_TTL_MS = 10 * 60 * 1000; const DEVELOPER_CACHE_TTL_MS = 10 * 60 * 1000;
let adminPlayersCache: { playerIds: string[]; expiresAt: number } | null = null; let developerPlayersCache: { playerIds: string[]; expiresAt: number } | null = null;
const getAdminPlayerIds = async (): Promise<string[]> => { const getDeveloperPlayerIds = async (): Promise<string[]> => {
const now = Date.now(); const now = Date.now();
if (adminPlayersCache && adminPlayersCache.expiresAt > now) { if (developerPlayersCache && developerPlayersCache.expiresAt > now) {
return adminPlayersCache.playerIds; return developerPlayersCache.playerIds;
} }
const UserRoles = (await import("supertokens-node/recipe/userroles")).default; const UserRoles = (await import("supertokens-node/recipe/userroles")).default;
const UserMetadata = (await import("supertokens-node/recipe/usermetadata")).default; const UserMetadata = (await import("supertokens-node/recipe/usermetadata")).default;
const response = await UserRoles.getUsersThatHaveRole("public", ADMIN_ROLE); const response = await UserRoles.getUsersThatHaveRole("public", DEVELOPER_ROLE);
const users = response.status === "OK" ? response.users : []; const users = response.status === "OK" ? response.users : [];
const playerIds: string[] = []; const playerIds: string[] = [];
for (const userId of users) { for (const userId of users) {
try { try {
// Opt-in: only admins who enabled telemetry alerts in settings. // Opt-in: only developers who enabled telemetry alerts in settings.
const { metadata } = await UserMetadata.getUserMetadata(userId); const { metadata } = await UserMetadata.getUserMetadata(userId);
if (metadata?.telemetryAlerts !== true) continue; if (metadata?.telemetryAlerts !== true) continue;
const player = await pbAdmin.getPlayerByAuthId(userId); const player = await pbAdmin.getPlayerByAuthId(userId);
@@ -46,7 +46,7 @@ const getAdminPlayerIds = async (): Promise<string[]> => {
} catch {} } catch {}
} }
adminPlayersCache = { playerIds, expiresAt: now + ADMIN_CACHE_TTL_MS }; developerPlayersCache = { playerIds, expiresAt: now + DEVELOPER_CACHE_TTL_MS };
return playerIds; return playerIds;
}; };
@@ -71,8 +71,8 @@ const fireAlert = async (candidate: AlertCandidate) => {
value: candidate.value, value: candidate.value,
}); });
const admins = await getAdminPlayerIds(); const developers = await getDeveloperPlayerIds();
for (const playerId of admins) { for (const playerId of developers) {
try { try {
await sendPushToPlayer(playerId, { await sendPushToPlayer(playerId, {
title: candidate.title, title: candidate.title,
+101 -105
View File
@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/features/matches/components/match-card.tsx:80 #: src/features/matches/components/match-card.tsx:83
msgid " (Losers)" msgid " (Losers)"
msgstr " (Verlierer)" msgstr " (Verlierer)"
@@ -93,7 +93,7 @@ msgstr "{0, plural, one {Spieler angemeldet} other {Spieler angemeldet}}"
#. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", }) #. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", })
#. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", }) #. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", })
#: src/features/tournaments/components/started-tournament/header.tsx:51 #: src/features/tournaments/components/started-tournament/header.tsx:56
msgid "{0} at {1}" msgid "{0} at {1}"
msgstr "{0} um {1}" msgstr "{0} um {1}"
@@ -228,7 +228,7 @@ msgstr "{namesList} und 1 weitere"
msgid "{size}-team knockout" msgid "{size}-team knockout"
msgstr "{size}-Team-K.-o.-Runde" msgstr "{size}-Team-K.-o.-Runde"
#: src/features/players/components/player-stats-table.tsx:446 #: src/features/players/components/player-stats-table.tsx:445
msgid "* Confidence penalty applied for players with <15 matches" msgid "* Confidence penalty applied for players with <15 matches"
msgstr "* Vertrauensabschlag für Spieler mit <15 Spielen" msgstr "* Vertrauensabschlag für Spieler mit <15 Spielen"
@@ -241,7 +241,7 @@ msgstr "* Falls nötig"
msgid "* PICKS shows correct picks / total picks made" msgid "* PICKS shows correct picks / total picks made"
msgstr "* PICKS zeigt richtige Picks / insgesamt abgegebene Picks" msgstr "* PICKS zeigt richtige Picks / insgesamt abgegebene Picks"
#: src/features/players/components/player-stats-table.tsx:449 #: src/features/players/components/player-stats-table.tsx:448
msgid "** Not an official rating" msgid "** Not an official rating"
msgstr "** Keine offizielle Wertung" msgstr "** Keine offizielle Wertung"
@@ -254,19 +254,19 @@ msgstr "&"
msgid "#{0}" msgid "#{0}"
msgstr "#{0}" msgstr "#{0}"
#: src/features/players/components/player-stats-table.tsx:416 #: src/features/players/components/player-stats-table.tsx:415
msgid "• <0>AC:</0> Average Cups Per Match" msgid "• <0>AC:</0> Average Cups Per Match"
msgstr "• <0>AC:</0> Durchschnittliche Becher pro Spiel" msgstr "• <0>AC:</0> Durchschnittliche Becher pro Spiel"
#: src/features/players/components/player-stats-table.tsx:419 #: src/features/players/components/player-stats-table.tsx:418
msgid "• <0>ACA:</0> Average Cups Against" msgid "• <0>ACA:</0> Average Cups Against"
msgstr "• <0>ACA:</0> Durchschnittlich kassierte Becher" msgstr "• <0>ACA:</0> Durchschnittlich kassierte Becher"
#: src/features/players/components/player-stats-table.tsx:413 #: src/features/players/components/player-stats-table.tsx:412
msgid "• <0>ALM:</0> Average Loss Margin" msgid "• <0>ALM:</0> Average Loss Margin"
msgstr "• <0>ALM:</0> Durchschnittlicher Rückstand bei Niederlage" msgstr "• <0>ALM:</0> Durchschnittlicher Rückstand bei Niederlage"
#: src/features/players/components/player-stats-table.tsx:410 #: src/features/players/components/player-stats-table.tsx:409
msgid "• <0>AWM:</0> Average Win Margin" msgid "• <0>AWM:</0> Average Win Margin"
msgstr "• <0>AWM:</0> Durchschnittlicher Vorsprung bei Sieg" msgstr "• <0>AWM:</0> Durchschnittlicher Vorsprung bei Sieg"
@@ -274,15 +274,15 @@ msgstr "• <0>AWM:</0> Durchschnittlicher Vorsprung bei Sieg"
msgid "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final" msgid "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final"
msgstr "• <0>Bracket-Reset:</0> wird nur gewertet, wenn dein Bracket ihn auslöst — zählt doppelt so viel wie das Finale" msgstr "• <0>Bracket-Reset:</0> wird nur gewertet, wenn dein Bracket ihn auslöst — zählt doppelt so viel wie das Finale"
#: src/features/players/components/player-stats-table.tsx:425 #: src/features/players/components/player-stats-table.tsx:424
msgid "• <0>CA:</0> Cups Against" msgid "• <0>CA:</0> Cups Against"
msgstr "• <0>CA:</0> Kassierte Becher" msgstr "• <0>CA:</0> Kassierte Becher"
#: src/features/players/components/player-stats-table.tsx:422 #: src/features/players/components/player-stats-table.tsx:421
msgid "• <0>CF:</0> Cups For" msgid "• <0>CF:</0> Cups For"
msgstr "• <0>CF:</0> Erzielte Becher" msgstr "• <0>CF:</0> Erzielte Becher"
#: src/features/players/components/player-stats-table.tsx:404 #: src/features/players/components/player-stats-table.tsx:403
msgid "• <0>L:</0> Losses" msgid "• <0>L:</0> Losses"
msgstr "• <0>L:</0> Niederlagen" msgstr "• <0>L:</0> Niederlagen"
@@ -290,23 +290,23 @@ msgstr "• <0>L:</0> Niederlagen"
msgid "• <0>Losers bracket:</0> 5, 10, 20, 40…" msgid "• <0>Losers bracket:</0> 5, 10, 20, 40…"
msgstr "• <0>Verlierer-Bracket:</0> 5, 10, 20, 40…" msgstr "• <0>Verlierer-Bracket:</0> 5, 10, 20, 40…"
#: src/features/players/components/player-stats-table.tsx:392 #: src/features/players/components/player-stats-table.tsx:391
msgid "• <0>M:</0> Matches" msgid "• <0>M:</0> Matches"
msgstr "• <0>M:</0> Spiele" msgstr "• <0>M:</0> Spiele"
#: src/features/players/components/player-stats-table.tsx:398 #: src/features/players/components/player-stats-table.tsx:397
msgid "• <0>MMR:</0> Matchmaking Rating" msgid "• <0>MMR:</0> Matchmaking Rating"
msgstr "• <0>MMR:</0> Matchmaking-Wertung" msgstr "• <0>MMR:</0> Matchmaking-Wertung"
#: src/features/players/components/player-stats-table.tsx:395 #: src/features/players/components/player-stats-table.tsx:394
msgid "• <0>T:</0> Tournaments" msgid "• <0>T:</0> Tournaments"
msgstr "• <0>T:</0> Turniere" msgstr "• <0>T:</0> Turniere"
#: src/features/players/components/player-stats-table.tsx:401 #: src/features/players/components/player-stats-table.tsx:400
msgid "• <0>W:</0> Wins" msgid "• <0>W:</0> Wins"
msgstr "• <0>W:</0> Siege" msgstr "• <0>W:</0> Siege"
#: src/features/players/components/player-stats-table.tsx:407 #: src/features/players/components/player-stats-table.tsx:406
msgid "• <0>W%:</0> Win Percentage" msgid "• <0>W%:</0> Win Percentage"
msgstr "• <0>W%:</0> Siegquote" msgstr "• <0>W%:</0> Siegquote"
@@ -314,11 +314,11 @@ msgstr "• <0>W%:</0> Siegquote"
msgid "• <0>Winners bracket:</0> 10, 20, 40, 80…" msgid "• <0>Winners bracket:</0> 10, 20, 40, 80…"
msgstr "• <0>Gewinner-Bracket:</0> 10, 20, 40, 80…" msgstr "• <0>Gewinner-Bracket:</0> 10, 20, 40, 80…"
#: src/features/players/components/player-stats-table.tsx:437 #: src/features/players/components/player-stats-table.tsx:436
msgid "• Average Cups/Match (25%)" msgid "• Average Cups/Match (25%)"
msgstr "• Durchschnittliche Becher/Spiel (25%)" msgstr "• Durchschnittliche Becher/Spiel (25%)"
#: src/features/players/components/player-stats-table.tsx:440 #: src/features/players/components/player-stats-table.tsx:439
msgid "• Average Win Margin (15%)" msgid "• Average Win Margin (15%)"
msgstr "• Durchschnittlicher Vorsprung bei Sieg (15%)" msgstr "• Durchschnittlicher Vorsprung bei Sieg (15%)"
@@ -326,11 +326,11 @@ msgstr "• Durchschnittlicher Vorsprung bei Sieg (15%)"
msgid "• Each correct pick earns points, doubling every round" msgid "• Each correct pick earns points, doubling every round"
msgstr "• Jeder richtige Tipp bringt Punkte, die sich jede Runde verdoppeln" msgstr "• Jeder richtige Tipp bringt Punkte, die sich jede Runde verdoppeln"
#: src/features/players/components/player-stats-table.tsx:443 #: src/features/players/components/player-stats-table.tsx:442
msgid "• Match Volume Bonus (10%)" msgid "• Match Volume Bonus (10%)"
msgstr "• Match-Volumen-Bonus (10%)" msgstr "• Match-Volumen-Bonus (10%)"
#: src/features/players/components/player-stats-table.tsx:434 #: src/features/players/components/player-stats-table.tsx:433
msgid "• Win Rate (50%)" msgid "• Win Rate (50%)"
msgstr "• Siegquote (50%)" msgstr "• Siegquote (50%)"
@@ -457,9 +457,9 @@ msgstr "Admin"
msgid "Aero" msgid "Aero"
msgstr "Aero" msgstr "Aero"
#: src/app/routes/_authed/stats.tsx:65 #: src/app/routes/_authed/stats.tsx:68
#: src/features/activities/components/activities-table.tsx:92 #: src/features/activities/components/activities-table.tsx:92
#: src/features/players/components/profile/index.tsx:35 #: src/features/players/components/profile/index.tsx:37
msgid "All" msgid "All"
msgstr "Alle" msgstr "Alle"
@@ -572,7 +572,7 @@ msgstr "Ø Sieg-Marge"
msgid "Award Badge" msgid "Award Badge"
msgstr "Abzeichen verleihen" msgstr "Abzeichen verleihen"
#: src/features/admin/components/dashboard/tools-list.tsx:60 #: src/features/admin/components/dashboard/tools-list.tsx:63
msgid "Award Badges" msgid "Award Badges"
msgstr "Abzeichen verleihen" msgstr "Abzeichen verleihen"
@@ -606,15 +606,15 @@ msgstr "Zurück"
msgid "Badge awarded to {playerName}" msgid "Badge awarded to {playerName}"
msgstr "Abzeichen an {playerName} verliehen" msgstr "Abzeichen an {playerName} verliehen"
#: src/features/admin/components/dashboard/tools-list.tsx:91 #: src/features/admin/components/dashboard/tools-list.tsx:94
msgid "Badge recalculation failed. Check the server logs." msgid "Badge recalculation failed. Check the server logs."
msgstr "Abzeichen-Neuberechnung fehlgeschlagen. Prüfe die Server-Logs." msgstr "Abzeichen-Neuberechnung fehlgeschlagen. Prüfe die Server-Logs."
#: src/features/admin/components/dashboard/tools-list.tsx:39 #: src/features/admin/components/dashboard/tools-list.tsx:40
msgid "Badge recalculation is already running" msgid "Badge recalculation is already running"
msgstr "Abzeichen-Neuberechnung läuft bereits" msgstr "Abzeichen-Neuberechnung läuft bereits"
#: src/features/admin/components/dashboard/tools-list.tsx:38 #: src/features/admin/components/dashboard/tools-list.tsx:39
msgid "Badge recalculation started in the background" msgid "Badge recalculation started in the background"
msgstr "Abzeichen-Neuberechnung im Hintergrund gestartet" msgstr "Abzeichen-Neuberechnung im Hintergrund gestartet"
@@ -622,7 +622,7 @@ msgstr "Abzeichen-Neuberechnung im Hintergrund gestartet"
msgid "Badge Type" msgid "Badge Type"
msgstr "Abzeichentyp" msgstr "Abzeichentyp"
#: src/features/players/components/profile/index.tsx:66 #: src/features/players/components/profile/index.tsx:68
msgid "Badges" msgid "Badges"
msgstr "Abzeichen" msgstr "Abzeichen"
@@ -645,7 +645,7 @@ msgid "Bracket"
msgstr "Turnierbaum" msgstr "Turnierbaum"
#: src/app/routes/_authed/admin/preview.tsx:13 #: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/dashboard/tools-list.tsx:102 #: src/features/admin/components/dashboard/tools-list.tsx:98
msgid "Bracket Preview" msgid "Bracket Preview"
msgstr "Turnierbaum-Vorschau" msgstr "Turnierbaum-Vorschau"
@@ -720,7 +720,7 @@ msgstr "Wähle einen Spieler"
msgid "Claymation" msgid "Claymation"
msgstr "Knete" msgstr "Knete"
#: src/features/players/components/league-head-to-head.tsx:102 #: src/features/players/components/league-head-to-head.tsx:107
msgid "Clear selected player" msgid "Clear selected player"
msgstr "Ausgewählten Spieler entfernen" msgstr "Ausgewählten Spieler entfernen"
@@ -1118,7 +1118,7 @@ msgstr "Fehlerspitzen und fehlschlagende Funktionen, als Push an dieses Konto"
msgid "Error: {errorMessage}" msgid "Error: {errorMessage}"
msgstr "Fehler: {errorMessage}" msgstr "Fehler: {errorMessage}"
#: src/app/routes/_authed/admin/telemetry.tsx:57 #: src/app/routes/_authed/admin/telemetry.tsx:63
#: src/features/telemetry/components/usage-screen.tsx:260 #: src/features/telemetry/components/usage-screen.tsx:260
msgid "Errors" msgid "Errors"
msgstr "Fehler" msgstr "Fehler"
@@ -1186,7 +1186,7 @@ msgstr "Aktives Gerät konnte nicht festgelegt werden"
msgid "Failed to set volume" msgid "Failed to set volume"
msgstr "Lautstärke konnte nicht festgelegt werden" msgstr "Lautstärke konnte nicht festgelegt werden"
#: src/features/admin/components/dashboard/tools-list.tsx:42 #: src/features/admin/components/dashboard/tools-list.tsx:43
msgid "Failed to start badge recalculation" msgid "Failed to start badge recalculation"
msgstr "Abzeichen-Neuberechnung konnte nicht gestartet werden" msgstr "Abzeichen-Neuberechnung konnte nicht gestartet werden"
@@ -1198,8 +1198,8 @@ msgstr "Team konnte nicht aktualisiert werden"
msgid "Failed to update tournament" msgid "Failed to update tournament"
msgstr "Turnier konnte nicht aktualisiert werden" msgstr "Turnier konnte nicht aktualisiert werden"
#: src/app/routes/api/teams/upload-logo.ts:141 #: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:139 #: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/features/teams/components/team-form/index.tsx:148 #: src/features/teams/components/team-form/index.tsx:148
#: src/features/tournaments/components/tournament-form.tsx:88 #: src/features/tournaments/components/tournament-form.tsx:88
msgid "Failed to upload logo" msgid "Failed to upload logo"
@@ -1209,8 +1209,8 @@ msgstr "Logo konnte nicht hochgeladen werden"
msgid "Failure" msgid "Failure"
msgstr "Fehler" msgstr "Fehler"
#: src/app/routes/api/teams/upload-logo.ts:80 #: src/app/routes/api/teams/upload-logo.ts:81
#: src/app/routes/api/tournaments/upload-logo.ts:81 #: src/app/routes/api/tournaments/upload-logo.ts:82
msgid "File too large. Maximum size is 10MB." msgid "File too large. Maximum size is 10MB."
msgstr "Datei zu groß. Maximale Größe ist 10 MB." msgstr "Datei zu groß. Maximale Größe ist 10 MB."
@@ -1264,7 +1264,7 @@ msgstr "Funktion"
msgid "Function Name" msgid "Function Name"
msgstr "Funktionsname" msgstr "Funktionsname"
#: src/app/routes/_authed/admin/telemetry.tsx:51 #: src/app/routes/_authed/admin/telemetry.tsx:57
msgid "Functions" msgid "Functions"
msgstr "Funktionen" msgstr "Funktionen"
@@ -1333,13 +1333,13 @@ msgstr "viel Spaß, viel Glück!"
msgid "Have you played before?" msgid "Have you played before?"
msgstr "Hast du schon mal mitgespielt?" msgstr "Hast du schon mal mitgespielt?"
#: src/app/routes/_authed/stats.tsx:53 #: src/app/routes/_authed/stats.tsx:56
#: src/features/bracket/components/match-dock.tsx:174 #: src/features/bracket/components/match-dock.tsx:174
#: src/features/bracket/components/match-dock.tsx:219 #: src/features/bracket/components/match-dock.tsx:203
#: src/features/matches/components/match-card.tsx:86 #: src/features/matches/components/match-card.tsx:89
#: src/features/matches/components/match-card.tsx:230 #: src/features/matches/components/match-card.tsx:214
#: src/features/players/components/league-head-to-head.tsx:193 #: src/features/players/components/league-head-to-head.tsx:198
#: src/features/players/components/profile/header.tsx:94 #: src/features/players/components/profile/header.tsx:79
msgid "Head to Head" msgid "Head to Head"
msgstr "Direktvergleich" msgstr "Direktvergleich"
@@ -1401,8 +1401,8 @@ msgstr "Falscher Code"
msgid "Invalid action" msgid "Invalid action"
msgstr "Ungültige Aktion" msgstr "Ungültige Aktion"
#: src/app/routes/api/teams/upload-logo.ts:66 #: src/app/routes/api/teams/upload-logo.ts:67
#: src/app/routes/api/tournaments/upload-logo.ts:67 #: src/app/routes/api/tournaments/upload-logo.ts:68
msgid "Invalid file type. Only JPEG, PNG and GIF are allowed." msgid "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgstr "Ungültiger Dateityp. Nur JPEG, PNG und GIF sind erlaubt." msgstr "Ungültiger Dateityp. Nur JPEG, PNG und GIF sind erlaubt."
@@ -1410,8 +1410,8 @@ msgstr "Ungültiger Dateityp. Nur JPEG, PNG und GIF sind erlaubt."
msgid "Invalid hex color format" msgid "Invalid hex color format"
msgstr "Ungültiges Hex-Farbformat" msgstr "Ungültiges Hex-Farbformat"
#: src/app/routes/api/teams/upload-logo.ts:34 #: src/app/routes/api/teams/upload-logo.ts:35
#: src/app/routes/api/tournaments/upload-logo.ts:35 #: src/app/routes/api/tournaments/upload-logo.ts:36
msgid "Invalid input" msgid "Invalid input"
msgstr "Ungültige Eingabe" msgstr "Ungültige Eingabe"
@@ -1470,7 +1470,7 @@ msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "Der Nachname muss 3-20 Zeichen lang sein und darf nur Buchstaben enthalten" msgstr "Der Nachname muss 3-20 Zeichen lang sein und darf nur Buchstaben enthalten"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned #. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/dashboard/tools-list.tsx:79 #: src/features/admin/components/dashboard/tools-list.tsx:82
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}" msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "Letzte Neuberechnung: {0, plural, one {# Abzeichen verdient} other {# Abzeichen verdient}}" msgstr "Letzte Neuberechnung: {0, plural, one {# Abzeichen verdient} other {# Abzeichen verdient}}"
@@ -1504,8 +1504,8 @@ msgstr "Ort ist erforderlich"
msgid "Logo" msgid "Logo"
msgstr "Logo" msgstr "Logo"
#: src/app/routes/api/teams/upload-logo.ts:47 #: src/app/routes/api/teams/upload-logo.ts:48
#: src/app/routes/api/tournaments/upload-logo.ts:48 #: src/app/routes/api/tournaments/upload-logo.ts:49
msgid "Logo file is required" msgid "Logo file is required"
msgstr "Logo-Datei ist erforderlich" msgstr "Logo-Datei ist erforderlich"
@@ -1530,8 +1530,8 @@ msgstr "Niederlagen"
msgid "M" msgid "M"
msgstr "S" msgstr "S"
#: src/app/routes/_authed/stats.tsx:72 #: src/app/routes/_authed/stats.tsx:75
#: src/features/players/components/profile/index.tsx:36 #: src/features/players/components/profile/index.tsx:38
msgid "Mainline" msgid "Mainline"
msgstr "Mainline" msgstr "Mainline"
@@ -1559,7 +1559,7 @@ msgid "Manage Team Songs/Logos"
msgstr "Team-Songs/Logos verwalten" msgstr "Team-Songs/Logos verwalten"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17 #: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/dashboard/tools-list.tsx:50 #: src/features/admin/components/dashboard/tools-list.tsx:51
msgid "Manage Tournaments" msgid "Manage Tournaments"
msgstr "Turniere verwalten" msgstr "Turniere verwalten"
@@ -1611,8 +1611,8 @@ msgstr "Match-Verlauf ({totalMatches})"
msgid "Match started!" msgid "Match started!"
msgstr "Match gestartet!" msgstr "Match gestartet!"
#: src/features/players/components/player-stats-table.tsx:376 #: src/features/players/components/player-stats-table.tsx:375
#: src/features/players/components/profile/index.tsx:85 #: src/features/players/components/profile/index.tsx:87
#: src/features/players/components/profile/skeleton.tsx:70 #: src/features/players/components/profile/skeleton.tsx:70
#: src/features/teams/components/team-profile/index.tsx:44 #: src/features/teams/components/team-profile/index.tsx:44
#: src/features/teams/components/team-profile/skeleton.tsx:75 #: src/features/teams/components/team-profile/skeleton.tsx:75
@@ -1621,7 +1621,7 @@ msgstr "Match gestartet!"
msgid "Matches" msgid "Matches"
msgstr "Matches" msgstr "Matches"
#: src/features/matches/components/match-list.tsx:33 #: src/features/matches/components/match-list.tsx:34
msgid "Matches for regionals are unordered" msgid "Matches for regionals are unordered"
msgstr "Matches für Regionals sind ungeordnet" msgstr "Matches für Regionals sind ungeordnet"
@@ -1630,11 +1630,11 @@ msgstr "Matches für Regionals sind ungeordnet"
msgid "Matches Played" msgid "Matches Played"
msgstr "Gespielte Matches" msgstr "Gespielte Matches"
#: src/features/predictions/components/prediction-editor.tsx:249 #: src/features/predictions/components/prediction-editor.tsx:253
msgid "Matchup" msgid "Matchup"
msgstr "Matchup" msgstr "Matchup"
#: src/features/predictions/components/prediction-editor.tsx:226 #: src/features/predictions/components/prediction-editor.tsx:230
msgid "Matchup details" msgid "Matchup details"
msgstr "Matchup-Details" msgstr "Matchup-Details"
@@ -1642,20 +1642,20 @@ msgstr "Matchup-Details"
msgid "MAU" msgid "MAU"
msgstr "MAU" msgstr "MAU"
#: src/features/players/components/league-head-to-head.tsx:123 #: src/features/players/components/league-head-to-head.tsx:128
msgid "Me" msgid "Me"
msgstr "Ich" msgstr "Ich"
#: src/features/admin/components/dashboard/tools-list.tsx:65 #: src/features/admin/components/dashboard/tools-list.tsx:68
msgid "Migrate Badge Progress" msgid "Migrate Badge Progress"
msgstr "Abzeichen-Fortschritt migrieren" msgstr "Abzeichen-Fortschritt migrieren"
#: src/features/players/components/player-stats-table.tsx:131 #: src/features/players/components/player-stats-table.tsx:131
#: src/features/players/components/player-stats-table.tsx:356 #: src/features/players/components/player-stats-table.tsx:355
msgid "MMR" msgid "MMR"
msgstr "MMR" msgstr "MMR"
#: src/features/players/components/player-stats-table.tsx:431 #: src/features/players/components/player-stats-table.tsx:430
msgid "MMR Calculation:" msgid "MMR Calculation:"
msgstr "MMR-Berechnung:" msgstr "MMR-Berechnung:"
@@ -1712,7 +1712,7 @@ msgstr "Neuer Client-Fehler"
msgid "New Player" msgid "New Player"
msgstr "Neuer Spieler" msgstr "Neuer Spieler"
#: src/features/predictions/components/prediction-editor.tsx:211 #: src/features/predictions/components/prediction-editor.tsx:212
msgid "Next match" msgid "Next match"
msgstr "Nächstes Match" msgstr "Nächstes Match"
@@ -1775,7 +1775,7 @@ msgstr "Keine Matches geplant"
msgid "No player activity found" msgid "No player activity found"
msgstr "Keine Spieleraktivität gefunden" msgstr "Keine Spieleraktivität gefunden"
#: src/features/players/components/league-head-to-head.tsx:151 #: src/features/players/components/league-head-to-head.tsx:156
msgid "No players available" msgid "No players available"
msgstr "Keine Spieler verfügbar" msgstr "Keine Spieler verfügbar"
@@ -1784,8 +1784,8 @@ msgstr "Keine Spieler verfügbar"
msgid "No players enrolled yet" msgid "No players enrolled yet"
msgstr "Noch keine Spieler angemeldet" msgstr "Noch keine Spieler angemeldet"
#: src/features/players/components/league-head-to-head.tsx:151 #: src/features/players/components/league-head-to-head.tsx:156
#: src/features/players/components/player-stats-table.tsx:486 #: src/features/players/components/player-stats-table.tsx:485
msgid "No players found matching \"{search}\"" msgid "No players found matching \"{search}\""
msgstr "Keine Spieler gefunden, die zu \"{search}\" passen" msgstr "Keine Spieler gefunden, die zu \"{search}\" passen"
@@ -1818,7 +1818,7 @@ msgstr "Kein Song ausgewählt"
msgid "No state captured" msgid "No state captured"
msgstr "Kein Status erfasst" msgstr "Kein Status erfasst"
#: src/features/players/components/player-stats-table.tsx:319 #: src/features/players/components/player-stats-table.tsx:318
msgid "No Stats Available" msgid "No Stats Available"
msgstr "Keine Statistiken verfügbar" msgstr "Keine Statistiken verfügbar"
@@ -1843,7 +1843,7 @@ msgstr "Noch keine Teams angemeldet"
msgid "No teams found matching \"{search}\"" msgid "No teams found matching \"{search}\""
msgstr "Keine Teams gefunden, die zu \"{search}\" passen" msgstr "Keine Teams gefunden, die zu \"{search}\" passen"
#: src/features/tournaments/components/tournament-list.tsx:95 #: src/features/tournaments/components/tournament-list.tsx:98
msgid "No tournaments found" msgid "No tournaments found"
msgstr "Keine Turniere gefunden" msgstr "Keine Turniere gefunden"
@@ -1908,10 +1908,6 @@ msgstr "Sobald du angemeldet bist, kannst du andere Free Agents und ihre Telefon
msgid "One time code" msgid "One time code"
msgstr "Einmalcode" msgstr "Einmalcode"
#: src/features/admin/components/dashboard/tools-list.tsx:95
msgid "Open Pocketbase"
msgstr "Pocketbase öffnen"
#: src/features/bracket/components/match-report.tsx:273 #: src/features/bracket/components/match-report.tsx:273
msgid "Opponent" msgid "Opponent"
msgstr "Gegner" msgstr "Gegner"
@@ -1937,7 +1933,7 @@ msgstr "Die Tipps anderer Spieler sind versteckt, bis das Turnier beginnt."
msgid "Overtime count is required" msgid "Overtime count is required"
msgstr "Anzahl der Verlängerungen ist erforderlich" msgstr "Anzahl der Verlängerungen ist erforderlich"
#: src/features/players/components/profile/index.tsx:61 #: src/features/players/components/profile/index.tsx:63
#: src/features/players/components/profile/skeleton.tsx:65 #: src/features/players/components/profile/skeleton.tsx:65
#: src/features/teams/components/team-profile/index.tsx:25 #: src/features/teams/components/team-profile/index.tsx:25
#: src/features/teams/components/team-profile/skeleton.tsx:70 #: src/features/teams/components/team-profile/skeleton.tsx:70
@@ -2047,7 +2043,7 @@ msgstr "Spieler erfolgreich entfernt"
msgid "Player Stats" msgid "Player Stats"
msgstr "Spielerstatistiken" msgstr "Spielerstatistiken"
#: src/app/routes/_authed/admin/telemetry.tsx:48 #: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/features/teams/components/team-profile/index.tsx:29 #: src/features/teams/components/team-profile/index.tsx:29
msgid "Players" msgid "Players"
msgstr "Spieler" msgstr "Spieler"
@@ -2083,7 +2079,7 @@ msgstr "Tipps"
msgid "Predictions are open" msgid "Predictions are open"
msgstr "Tipps sind offen" msgstr "Tipps sind offen"
#: src/features/predictions/components/prediction-editor.tsx:201 #: src/features/predictions/components/prediction-editor.tsx:202
msgid "Previous match" msgid "Previous match"
msgstr "Vorheriges Spiel" msgstr "Vorheriges Spiel"
@@ -2095,7 +2091,7 @@ msgstr "Vorheriger Titel"
msgid "Profile" msgid "Profile"
msgstr "Profil" msgstr "Profil"
#: src/features/badges/components/badge-showcase.tsx:301 #: src/features/badges/components/badge-showcase.tsx:302
msgid "Progress " msgid "Progress "
msgstr "Fortschritt " msgstr "Fortschritt "
@@ -2116,13 +2112,13 @@ msgstr "Questlog"
msgid "Re-roll" msgid "Re-roll"
msgstr "Neu würfeln" msgstr "Neu würfeln"
#: src/features/reactions/components/emoji-bar.tsx:213 #: src/features/reactions/components/emoji-bar.tsx:210
msgid "Reactions" msgid "Reactions"
msgstr "Reaktionen" msgstr "Reaktionen"
#. placeholder {0}: migrationStatus.playersProcessed #. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers #. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/dashboard/tools-list.tsx:72 #: src/features/admin/components/dashboard/tools-list.tsx:75
msgid "Recalculating badges in the background ({0}/{1})" msgid "Recalculating badges in the background ({0}/{1})"
msgstr "Abzeichen werden im Hintergrund neu berechnet ({0}/{1})" msgstr "Abzeichen werden im Hintergrund neu berechnet ({0}/{1})"
@@ -2130,8 +2126,8 @@ msgstr "Abzeichen werden im Hintergrund neu berechnet ({0}/{1})"
msgid "Recently finished" msgid "Recently finished"
msgstr "Kürzlich beendet" msgstr "Kürzlich beendet"
#: src/app/routes/_authed/stats.tsx:79 #: src/app/routes/_authed/stats.tsx:82
#: src/features/players/components/profile/index.tsx:37 #: src/features/players/components/profile/index.tsx:39
msgid "Regional" msgid "Regional"
msgstr "Regional" msgstr "Regional"
@@ -2182,7 +2178,7 @@ msgid "rofl"
msgstr "rofl" msgstr "rofl"
#. placeholder {0}: match.round + 1 #. placeholder {0}: match.round + 1
#: src/features/matches/components/match-card.tsx:79 #: src/features/matches/components/match-card.tsx:82
msgid "Round {0}" msgid "Round {0}"
msgstr "Runde {0}" msgstr "Runde {0}"
@@ -2245,8 +2241,8 @@ msgstr "Nach Songs suchen..."
msgid "Search for teams to enroll..." msgid "Search for teams to enroll..."
msgstr "Teams zum Anmelden suchen..." msgstr "Teams zum Anmelden suchen..."
#: src/features/players/components/league-head-to-head.tsx:132 #: src/features/players/components/league-head-to-head.tsx:137
#: src/features/players/components/player-stats-table.tsx:332 #: src/features/players/components/player-stats-table.tsx:331
msgid "Search players" msgid "Search players"
msgstr "Spieler suchen" msgstr "Spieler suchen"
@@ -2283,7 +2279,7 @@ msgstr "Abzeichen auswählen"
msgid "Select a device..." msgid "Select a device..."
msgstr "Gerät auswählen..." msgstr "Gerät auswählen..."
#: src/features/players/components/league-head-to-head.tsx:112 #: src/features/players/components/league-head-to-head.tsx:117
msgid "Select a player" msgid "Select a player"
msgstr "Wähle einen Spieler" msgstr "Wähle einen Spieler"
@@ -2366,7 +2362,7 @@ msgstr "Serverfunktionen"
msgid "serverFn name" msgid "serverFn name"
msgstr "serverFn-Name" msgstr "serverFn-Name"
#: src/app/routes/_authed/admin/telemetry.tsx:54 #: src/app/routes/_authed/admin/telemetry.tsx:60
#: src/features/telemetry/components/usage-screen.tsx:212 #: src/features/telemetry/components/usage-screen.tsx:212
msgid "Sessions" msgid "Sessions"
msgstr "Sitzungen" msgstr "Sitzungen"
@@ -2382,7 +2378,7 @@ msgstr "Details anzeigen"
#. placeholder {0}: filteredAndSortedStats.length #. placeholder {0}: filteredAndSortedStats.length
#. placeholder {1}: playerStats.length #. placeholder {1}: playerStats.length
#: src/features/players/components/player-stats-table.tsx:329 #: src/features/players/components/player-stats-table.tsx:328
msgid "Showing {0} of {1} players" msgid "Showing {0} of {1} players"
msgstr "{0} von {1} Spielern angezeigt" msgstr "{0} von {1} Spielern angezeigt"
@@ -2434,7 +2430,7 @@ msgstr "Song-Segment darf höchstens 15 Sekunden lang sein"
msgid "Song start time is required" msgid "Song start time is required"
msgstr "Startzeit des Songs ist erforderlich" msgstr "Startzeit des Songs ist erforderlich"
#: src/features/players/components/player-stats-table.tsx:350 #: src/features/players/components/player-stats-table.tsx:349
#: src/features/telemetry/components/shared/search-filter-bar.tsx:102 #: src/features/telemetry/components/shared/search-filter-bar.tsx:102
msgid "Sort:" msgid "Sort:"
msgstr "Sortieren:" msgstr "Sortieren:"
@@ -2489,21 +2485,21 @@ msgstr "Tippen, um zu suchen …"
msgid "Starts in" msgid "Starts in"
msgstr "Beginnt in" msgstr "Beginnt in"
#: src/features/players/components/player-stats-table.tsx:382 #: src/features/players/components/player-stats-table.tsx:381
msgid "Stat abbreviations and MMR info" msgid "Stat abbreviations and MMR info"
msgstr "Stat-Abkürzungen und MMR-Infos" msgstr "Stat-Abkürzungen und MMR-Infos"
#: src/features/players/components/player-stats-table.tsx:389 #: src/features/players/components/player-stats-table.tsx:388
msgid "Stat Abbreviations:" msgid "Stat Abbreviations:"
msgstr "Stat-Abkürzungen:" msgstr "Stat-Abkürzungen:"
#: src/features/core/hooks/use-links.ts:17 #: src/features/core/hooks/use-links.ts:17
#: src/features/players/components/profile/index.tsx:33 #: src/features/players/components/profile/index.tsx:35
#: src/features/teams/components/team-profile/index.tsx:34 #: src/features/teams/components/team-profile/index.tsx:34
msgid "Statistics" msgid "Statistics"
msgstr "Statistiken" msgstr "Statistiken"
#: src/app/routes/_authed/stats.tsx:52 #: src/app/routes/_authed/stats.tsx:55
msgid "Stats" msgid "Stats"
msgstr "Stats" msgstr "Stats"
@@ -2520,7 +2516,7 @@ msgstr "Status"
#: src/components/sheet/slide-panel/slide-panel.tsx:40 #: src/components/sheet/slide-panel/slide-panel.tsx:40
#: src/features/login/components/player-prompt/index.tsx:115 #: src/features/login/components/player-prompt/index.tsx:115
#: src/features/login/components/player-prompt/index.tsx:126 #: src/features/login/components/player-prompt/index.tsx:126
#: src/features/predictions/components/prediction-editor.tsx:240 #: src/features/predictions/components/prediction-editor.tsx:244
#: src/features/tournaments/components/edit-rules.tsx:39 #: src/features/tournaments/components/edit-rules.tsx:39
msgid "Submit" msgid "Submit"
msgstr "Absenden" msgstr "Absenden"
@@ -2543,7 +2539,7 @@ msgstr "System"
msgid "T" msgid "T"
msgstr "T" msgstr "T"
#: src/features/players/components/league-head-to-head.tsx:92 #: src/features/players/components/league-head-to-head.tsx:97
msgid "Tap an opponent below to compare" msgid "Tap an opponent below to compare"
msgstr "Tippe unten auf einen Gegner zum Vergleichen" msgstr "Tippe unten auf einen Gegner zum Vergleichen"
@@ -2581,7 +2577,7 @@ msgstr "Team-Details"
msgid "Team enrolled successfully!" msgid "Team enrolled successfully!"
msgstr "Team erfolgreich angemeldet!" msgstr "Team erfolgreich angemeldet!"
#: src/app/routes/api/teams/upload-logo.ts:93 #: src/app/routes/api/teams/upload-logo.ts:94
#: src/features/teams/components/team-profile/index.tsx:21 #: src/features/teams/components/team-profile/index.tsx:21
msgid "Team not found" msgid "Team not found"
msgstr "Team nicht gefunden" msgstr "Team nicht gefunden"
@@ -2603,7 +2599,7 @@ msgstr "Team aktualisiert, aber Logo-Upload fehlgeschlagen: {0}"
msgid "Team updated successfully!" msgid "Team updated successfully!"
msgstr "Team erfolgreich aktualisiert!" msgstr "Team erfolgreich aktualisiert!"
#: src/features/players/components/profile/index.tsx:90 #: src/features/players/components/profile/index.tsx:92
#: src/features/players/components/profile/skeleton.tsx:75 #: src/features/players/components/profile/skeleton.tsx:75
#: src/features/tournaments/components/profile/index.tsx:41 #: src/features/tournaments/components/profile/index.tsx:41
#: src/features/tournaments/components/profile/skeleton.tsx:89 #: src/features/tournaments/components/profile/skeleton.tsx:89
@@ -2615,8 +2611,8 @@ msgstr "Teams"
msgid "Teams created successfully!" msgid "Teams created successfully!"
msgstr "Teams erfolgreich erstellt!" msgstr "Teams erfolgreich erstellt!"
#: src/app/routes/_authed/admin/telemetry.tsx:20 #: src/app/routes/_authed/admin/telemetry.tsx:26
#: src/features/admin/components/dashboard/tools-list.tsx:55 #: src/features/admin/components/dashboard/tools-list.tsx:57
msgid "Telemetry" msgid "Telemetry"
msgstr "Telemetrie" msgstr "Telemetrie"
@@ -2730,7 +2726,7 @@ msgstr "Turnier erstellt, aber Logo-Upload fehlgeschlagen: {0}"
msgid "Tournament created successfully!" msgid "Tournament created successfully!"
msgstr "Turnier erfolgreich erstellt!" msgstr "Turnier erfolgreich erstellt!"
#: src/app/routes/api/tournaments/upload-logo.ts:94 #: src/app/routes/api/tournaments/upload-logo.ts:95
msgid "Tournament not found" msgid "Tournament not found"
msgstr "Turnier nicht gefunden" msgstr "Turnier nicht gefunden"
@@ -2784,8 +2780,8 @@ msgstr "Team abmelden"
msgid "Unknown error" msgid "Unknown error"
msgstr "Unbekannter Fehler" msgstr "Unbekannter Fehler"
#: src/app/routes/api/teams/upload-logo.ts:142 #: src/app/routes/api/teams/upload-logo.ts:143
#: src/app/routes/api/tournaments/upload-logo.ts:140 #: src/app/routes/api/tournaments/upload-logo.ts:141
msgid "Unknown error occurred" msgid "Unknown error occurred"
msgstr "Unbekannter Fehler aufgetreten" msgstr "Unbekannter Fehler aufgetreten"
@@ -2810,7 +2806,7 @@ msgstr "Bevorstehend"
msgid "Update Match" msgid "Update Match"
msgstr "Match aktualisieren" msgstr "Match aktualisieren"
#: src/features/players/components/profile/header.tsx:89 #: src/features/players/components/profile/header.tsx:74
msgid "Update Name" msgid "Update Name"
msgstr "Namen aktualisieren" msgstr "Namen aktualisieren"
@@ -2824,7 +2820,7 @@ msgstr "Team aktualisieren"
msgid "Update Tournament" msgid "Update Tournament"
msgstr "Turnier aktualisieren" msgstr "Turnier aktualisieren"
#: src/app/routes/_authed/admin/telemetry.tsx:45 #: src/app/routes/_authed/admin/telemetry.tsx:51
msgid "Usage" msgid "Usage"
msgstr "Nutzung" msgstr "Nutzung"
@@ -2837,7 +2833,7 @@ msgstr "User-Agent"
msgid "versus" msgid "versus"
msgstr "gegen" msgstr "gegen"
#: src/features/players/components/profile/index.tsx:73 #: src/features/players/components/profile/index.tsx:75
msgid "View all badges" msgid "View all badges"
msgstr "Alle Abzeichen ansehen" msgstr "Alle Abzeichen ansehen"
@@ -2853,8 +2849,8 @@ msgstr "Turnierbaum ansehen"
msgid "View Groups" msgid "View Groups"
msgstr "Gruppen ansehen" msgstr "Gruppen ansehen"
#: src/features/bracket/components/match-dock.tsx:182 #: src/features/bracket/components/match-dock.tsx:187
#: src/features/matches/components/match-card.tsx:91 #: src/features/matches/components/match-card.tsx:94
msgid "View head-to-head" msgid "View head-to-head"
msgstr "Direktvergleich ansehen" msgstr "Direktvergleich ansehen"
@@ -2935,7 +2931,7 @@ msgstr "Finale der Gewinnerrunde"
#: src/components/stats-overview.tsx:103 #: src/components/stats-overview.tsx:103
#: src/components/stats-overview.tsx:136 #: src/components/stats-overview.tsx:136
#: src/features/players/components/player-stats-table.tsx:366 #: src/features/players/components/player-stats-table.tsx:365
msgid "Wins" msgid "Wins"
msgstr "Siege" msgstr "Siege"
+101 -105
View File
@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/features/matches/components/match-card.tsx:80 #: src/features/matches/components/match-card.tsx:83
msgid " (Losers)" msgid " (Losers)"
msgstr " (Losers)" msgstr " (Losers)"
@@ -93,7 +93,7 @@ msgstr "{0, plural, one {player enrolled} other {players enrolled}}"
#. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", }) #. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", })
#. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", }) #. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", })
#: src/features/tournaments/components/started-tournament/header.tsx:51 #: src/features/tournaments/components/started-tournament/header.tsx:56
msgid "{0} at {1}" msgid "{0} at {1}"
msgstr "{0} at {1}" msgstr "{0} at {1}"
@@ -228,7 +228,7 @@ msgstr "{namesList} and 1 other"
msgid "{size}-team knockout" msgid "{size}-team knockout"
msgstr "{size}-team knockout" msgstr "{size}-team knockout"
#: src/features/players/components/player-stats-table.tsx:446 #: src/features/players/components/player-stats-table.tsx:445
msgid "* Confidence penalty applied for players with <15 matches" msgid "* Confidence penalty applied for players with <15 matches"
msgstr "* Confidence penalty applied for players with <15 matches" msgstr "* Confidence penalty applied for players with <15 matches"
@@ -241,7 +241,7 @@ msgstr "* If necessary"
msgid "* PICKS shows correct picks / total picks made" msgid "* PICKS shows correct picks / total picks made"
msgstr "* PICKS shows correct picks / total picks made" msgstr "* PICKS shows correct picks / total picks made"
#: src/features/players/components/player-stats-table.tsx:449 #: src/features/players/components/player-stats-table.tsx:448
msgid "** Not an official rating" msgid "** Not an official rating"
msgstr "** Not an official rating" msgstr "** Not an official rating"
@@ -254,19 +254,19 @@ msgstr "&"
msgid "#{0}" msgid "#{0}"
msgstr "#{0}" msgstr "#{0}"
#: src/features/players/components/player-stats-table.tsx:416 #: src/features/players/components/player-stats-table.tsx:415
msgid "• <0>AC:</0> Average Cups Per Match" msgid "• <0>AC:</0> Average Cups Per Match"
msgstr "• <0>AC:</0> Average Cups Per Match" msgstr "• <0>AC:</0> Average Cups Per Match"
#: src/features/players/components/player-stats-table.tsx:419 #: src/features/players/components/player-stats-table.tsx:418
msgid "• <0>ACA:</0> Average Cups Against" msgid "• <0>ACA:</0> Average Cups Against"
msgstr "• <0>ACA:</0> Average Cups Against" msgstr "• <0>ACA:</0> Average Cups Against"
#: src/features/players/components/player-stats-table.tsx:413 #: src/features/players/components/player-stats-table.tsx:412
msgid "• <0>ALM:</0> Average Loss Margin" msgid "• <0>ALM:</0> Average Loss Margin"
msgstr "• <0>ALM:</0> Average Loss Margin" msgstr "• <0>ALM:</0> Average Loss Margin"
#: src/features/players/components/player-stats-table.tsx:410 #: src/features/players/components/player-stats-table.tsx:409
msgid "• <0>AWM:</0> Average Win Margin" msgid "• <0>AWM:</0> Average Win Margin"
msgstr "• <0>AWM:</0> Average Win Margin" msgstr "• <0>AWM:</0> Average Win Margin"
@@ -274,15 +274,15 @@ msgstr "• <0>AWM:</0> Average Win Margin"
msgid "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final" msgid "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final"
msgstr "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final" msgstr "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final"
#: src/features/players/components/player-stats-table.tsx:425 #: src/features/players/components/player-stats-table.tsx:424
msgid "• <0>CA:</0> Cups Against" msgid "• <0>CA:</0> Cups Against"
msgstr "• <0>CA:</0> Cups Against" msgstr "• <0>CA:</0> Cups Against"
#: src/features/players/components/player-stats-table.tsx:422 #: src/features/players/components/player-stats-table.tsx:421
msgid "• <0>CF:</0> Cups For" msgid "• <0>CF:</0> Cups For"
msgstr "• <0>CF:</0> Cups For" msgstr "• <0>CF:</0> Cups For"
#: src/features/players/components/player-stats-table.tsx:404 #: src/features/players/components/player-stats-table.tsx:403
msgid "• <0>L:</0> Losses" msgid "• <0>L:</0> Losses"
msgstr "• <0>L:</0> Losses" msgstr "• <0>L:</0> Losses"
@@ -290,23 +290,23 @@ msgstr "• <0>L:</0> Losses"
msgid "• <0>Losers bracket:</0> 5, 10, 20, 40…" msgid "• <0>Losers bracket:</0> 5, 10, 20, 40…"
msgstr "• <0>Losers bracket:</0> 5, 10, 20, 40…" msgstr "• <0>Losers bracket:</0> 5, 10, 20, 40…"
#: src/features/players/components/player-stats-table.tsx:392 #: src/features/players/components/player-stats-table.tsx:391
msgid "• <0>M:</0> Matches" msgid "• <0>M:</0> Matches"
msgstr "• <0>M:</0> Matches" msgstr "• <0>M:</0> Matches"
#: src/features/players/components/player-stats-table.tsx:398 #: src/features/players/components/player-stats-table.tsx:397
msgid "• <0>MMR:</0> Matchmaking Rating" msgid "• <0>MMR:</0> Matchmaking Rating"
msgstr "• <0>MMR:</0> Matchmaking Rating" msgstr "• <0>MMR:</0> Matchmaking Rating"
#: src/features/players/components/player-stats-table.tsx:395 #: src/features/players/components/player-stats-table.tsx:394
msgid "• <0>T:</0> Tournaments" msgid "• <0>T:</0> Tournaments"
msgstr "• <0>T:</0> Tournaments" msgstr "• <0>T:</0> Tournaments"
#: src/features/players/components/player-stats-table.tsx:401 #: src/features/players/components/player-stats-table.tsx:400
msgid "• <0>W:</0> Wins" msgid "• <0>W:</0> Wins"
msgstr "• <0>W:</0> Wins" msgstr "• <0>W:</0> Wins"
#: src/features/players/components/player-stats-table.tsx:407 #: src/features/players/components/player-stats-table.tsx:406
msgid "• <0>W%:</0> Win Percentage" msgid "• <0>W%:</0> Win Percentage"
msgstr "• <0>W%:</0> Win Percentage" msgstr "• <0>W%:</0> Win Percentage"
@@ -314,11 +314,11 @@ msgstr "• <0>W%:</0> Win Percentage"
msgid "• <0>Winners bracket:</0> 10, 20, 40, 80…" msgid "• <0>Winners bracket:</0> 10, 20, 40, 80…"
msgstr "• <0>Winners bracket:</0> 10, 20, 40, 80…" msgstr "• <0>Winners bracket:</0> 10, 20, 40, 80…"
#: src/features/players/components/player-stats-table.tsx:437 #: src/features/players/components/player-stats-table.tsx:436
msgid "• Average Cups/Match (25%)" msgid "• Average Cups/Match (25%)"
msgstr "• Average Cups/Match (25%)" msgstr "• Average Cups/Match (25%)"
#: src/features/players/components/player-stats-table.tsx:440 #: src/features/players/components/player-stats-table.tsx:439
msgid "• Average Win Margin (15%)" msgid "• Average Win Margin (15%)"
msgstr "• Average Win Margin (15%)" msgstr "• Average Win Margin (15%)"
@@ -326,11 +326,11 @@ msgstr "• Average Win Margin (15%)"
msgid "• Each correct pick earns points, doubling every round" msgid "• Each correct pick earns points, doubling every round"
msgstr "• Each correct pick earns points, doubling every round" msgstr "• Each correct pick earns points, doubling every round"
#: src/features/players/components/player-stats-table.tsx:443 #: src/features/players/components/player-stats-table.tsx:442
msgid "• Match Volume Bonus (10%)" msgid "• Match Volume Bonus (10%)"
msgstr "• Match Volume Bonus (10%)" msgstr "• Match Volume Bonus (10%)"
#: src/features/players/components/player-stats-table.tsx:434 #: src/features/players/components/player-stats-table.tsx:433
msgid "• Win Rate (50%)" msgid "• Win Rate (50%)"
msgstr "• Win Rate (50%)" msgstr "• Win Rate (50%)"
@@ -457,9 +457,9 @@ msgstr "Admin"
msgid "Aero" msgid "Aero"
msgstr "Aero" msgstr "Aero"
#: src/app/routes/_authed/stats.tsx:65 #: src/app/routes/_authed/stats.tsx:68
#: src/features/activities/components/activities-table.tsx:92 #: src/features/activities/components/activities-table.tsx:92
#: src/features/players/components/profile/index.tsx:35 #: src/features/players/components/profile/index.tsx:37
msgid "All" msgid "All"
msgstr "All" msgstr "All"
@@ -572,7 +572,7 @@ msgstr "Avg Win Margin"
msgid "Award Badge" msgid "Award Badge"
msgstr "Award Badge" msgstr "Award Badge"
#: src/features/admin/components/dashboard/tools-list.tsx:60 #: src/features/admin/components/dashboard/tools-list.tsx:63
msgid "Award Badges" msgid "Award Badges"
msgstr "Award Badges" msgstr "Award Badges"
@@ -606,15 +606,15 @@ msgstr "Back"
msgid "Badge awarded to {playerName}" msgid "Badge awarded to {playerName}"
msgstr "Badge awarded to {playerName}" msgstr "Badge awarded to {playerName}"
#: src/features/admin/components/dashboard/tools-list.tsx:91 #: src/features/admin/components/dashboard/tools-list.tsx:94
msgid "Badge recalculation failed. Check the server logs." msgid "Badge recalculation failed. Check the server logs."
msgstr "Badge recalculation failed. Check the server logs." msgstr "Badge recalculation failed. Check the server logs."
#: src/features/admin/components/dashboard/tools-list.tsx:39 #: src/features/admin/components/dashboard/tools-list.tsx:40
msgid "Badge recalculation is already running" msgid "Badge recalculation is already running"
msgstr "Badge recalculation is already running" msgstr "Badge recalculation is already running"
#: src/features/admin/components/dashboard/tools-list.tsx:38 #: src/features/admin/components/dashboard/tools-list.tsx:39
msgid "Badge recalculation started in the background" msgid "Badge recalculation started in the background"
msgstr "Badge recalculation started in the background" msgstr "Badge recalculation started in the background"
@@ -622,7 +622,7 @@ msgstr "Badge recalculation started in the background"
msgid "Badge Type" msgid "Badge Type"
msgstr "Badge Type" msgstr "Badge Type"
#: src/features/players/components/profile/index.tsx:66 #: src/features/players/components/profile/index.tsx:68
msgid "Badges" msgid "Badges"
msgstr "Badges" msgstr "Badges"
@@ -645,7 +645,7 @@ msgid "Bracket"
msgstr "Bracket" msgstr "Bracket"
#: src/app/routes/_authed/admin/preview.tsx:13 #: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/dashboard/tools-list.tsx:102 #: src/features/admin/components/dashboard/tools-list.tsx:98
msgid "Bracket Preview" msgid "Bracket Preview"
msgstr "Bracket Preview" msgstr "Bracket Preview"
@@ -720,7 +720,7 @@ msgstr "Choose a player"
msgid "Claymation" msgid "Claymation"
msgstr "Claymation" msgstr "Claymation"
#: src/features/players/components/league-head-to-head.tsx:102 #: src/features/players/components/league-head-to-head.tsx:107
msgid "Clear selected player" msgid "Clear selected player"
msgstr "Clear selected player" msgstr "Clear selected player"
@@ -1118,7 +1118,7 @@ msgstr "Error spikes and failing functions, pushed to this account"
msgid "Error: {errorMessage}" msgid "Error: {errorMessage}"
msgstr "Error: {errorMessage}" msgstr "Error: {errorMessage}"
#: src/app/routes/_authed/admin/telemetry.tsx:57 #: src/app/routes/_authed/admin/telemetry.tsx:63
#: src/features/telemetry/components/usage-screen.tsx:260 #: src/features/telemetry/components/usage-screen.tsx:260
msgid "Errors" msgid "Errors"
msgstr "Errors" msgstr "Errors"
@@ -1186,7 +1186,7 @@ msgstr "Failed to set active device"
msgid "Failed to set volume" msgid "Failed to set volume"
msgstr "Failed to set volume" msgstr "Failed to set volume"
#: src/features/admin/components/dashboard/tools-list.tsx:42 #: src/features/admin/components/dashboard/tools-list.tsx:43
msgid "Failed to start badge recalculation" msgid "Failed to start badge recalculation"
msgstr "Failed to start badge recalculation" msgstr "Failed to start badge recalculation"
@@ -1198,8 +1198,8 @@ msgstr "Failed to update team"
msgid "Failed to update tournament" msgid "Failed to update tournament"
msgstr "Failed to update tournament" msgstr "Failed to update tournament"
#: src/app/routes/api/teams/upload-logo.ts:141 #: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:139 #: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/features/teams/components/team-form/index.tsx:148 #: src/features/teams/components/team-form/index.tsx:148
#: src/features/tournaments/components/tournament-form.tsx:88 #: src/features/tournaments/components/tournament-form.tsx:88
msgid "Failed to upload logo" msgid "Failed to upload logo"
@@ -1209,8 +1209,8 @@ msgstr "Failed to upload logo"
msgid "Failure" msgid "Failure"
msgstr "Failure" msgstr "Failure"
#: src/app/routes/api/teams/upload-logo.ts:80 #: src/app/routes/api/teams/upload-logo.ts:81
#: src/app/routes/api/tournaments/upload-logo.ts:81 #: src/app/routes/api/tournaments/upload-logo.ts:82
msgid "File too large. Maximum size is 10MB." msgid "File too large. Maximum size is 10MB."
msgstr "File too large. Maximum size is 10MB." msgstr "File too large. Maximum size is 10MB."
@@ -1264,7 +1264,7 @@ msgstr "Function"
msgid "Function Name" msgid "Function Name"
msgstr "Function Name" msgstr "Function Name"
#: src/app/routes/_authed/admin/telemetry.tsx:51 #: src/app/routes/_authed/admin/telemetry.tsx:57
msgid "Functions" msgid "Functions"
msgstr "Functions" msgstr "Functions"
@@ -1333,13 +1333,13 @@ msgstr "have fun, good luck!"
msgid "Have you played before?" msgid "Have you played before?"
msgstr "Have you played before?" msgstr "Have you played before?"
#: src/app/routes/_authed/stats.tsx:53 #: src/app/routes/_authed/stats.tsx:56
#: src/features/bracket/components/match-dock.tsx:174 #: src/features/bracket/components/match-dock.tsx:174
#: src/features/bracket/components/match-dock.tsx:219 #: src/features/bracket/components/match-dock.tsx:203
#: src/features/matches/components/match-card.tsx:86 #: src/features/matches/components/match-card.tsx:89
#: src/features/matches/components/match-card.tsx:230 #: src/features/matches/components/match-card.tsx:214
#: src/features/players/components/league-head-to-head.tsx:193 #: src/features/players/components/league-head-to-head.tsx:198
#: src/features/players/components/profile/header.tsx:94 #: src/features/players/components/profile/header.tsx:79
msgid "Head to Head" msgid "Head to Head"
msgstr "Head to Head" msgstr "Head to Head"
@@ -1401,8 +1401,8 @@ msgstr "Incorrect code"
msgid "Invalid action" msgid "Invalid action"
msgstr "Invalid action" msgstr "Invalid action"
#: src/app/routes/api/teams/upload-logo.ts:66 #: src/app/routes/api/teams/upload-logo.ts:67
#: src/app/routes/api/tournaments/upload-logo.ts:67 #: src/app/routes/api/tournaments/upload-logo.ts:68
msgid "Invalid file type. Only JPEG, PNG and GIF are allowed." msgid "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgstr "Invalid file type. Only JPEG, PNG and GIF are allowed." msgstr "Invalid file type. Only JPEG, PNG and GIF are allowed."
@@ -1410,8 +1410,8 @@ msgstr "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgid "Invalid hex color format" msgid "Invalid hex color format"
msgstr "Invalid hex color format" msgstr "Invalid hex color format"
#: src/app/routes/api/teams/upload-logo.ts:34 #: src/app/routes/api/teams/upload-logo.ts:35
#: src/app/routes/api/tournaments/upload-logo.ts:35 #: src/app/routes/api/tournaments/upload-logo.ts:36
msgid "Invalid input" msgid "Invalid input"
msgstr "Invalid input" msgstr "Invalid input"
@@ -1470,7 +1470,7 @@ msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "Last name must be 3-20 characters long and contain only letters" msgstr "Last name must be 3-20 characters long and contain only letters"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned #. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/dashboard/tools-list.tsx:79 #: src/features/admin/components/dashboard/tools-list.tsx:82
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}" msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}" msgstr "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
@@ -1504,8 +1504,8 @@ msgstr "Location is required"
msgid "Logo" msgid "Logo"
msgstr "Logo" msgstr "Logo"
#: src/app/routes/api/teams/upload-logo.ts:47 #: src/app/routes/api/teams/upload-logo.ts:48
#: src/app/routes/api/tournaments/upload-logo.ts:48 #: src/app/routes/api/tournaments/upload-logo.ts:49
msgid "Logo file is required" msgid "Logo file is required"
msgstr "Logo file is required" msgstr "Logo file is required"
@@ -1530,8 +1530,8 @@ msgstr "Losses"
msgid "M" msgid "M"
msgstr "M" msgstr "M"
#: src/app/routes/_authed/stats.tsx:72 #: src/app/routes/_authed/stats.tsx:75
#: src/features/players/components/profile/index.tsx:36 #: src/features/players/components/profile/index.tsx:38
msgid "Mainline" msgid "Mainline"
msgstr "Mainline" msgstr "Mainline"
@@ -1559,7 +1559,7 @@ msgid "Manage Team Songs/Logos"
msgstr "Manage Team Songs/Logos" msgstr "Manage Team Songs/Logos"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17 #: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/dashboard/tools-list.tsx:50 #: src/features/admin/components/dashboard/tools-list.tsx:51
msgid "Manage Tournaments" msgid "Manage Tournaments"
msgstr "Manage Tournaments" msgstr "Manage Tournaments"
@@ -1611,8 +1611,8 @@ msgstr "Match History ({totalMatches})"
msgid "Match started!" msgid "Match started!"
msgstr "Match started!" msgstr "Match started!"
#: src/features/players/components/player-stats-table.tsx:376 #: src/features/players/components/player-stats-table.tsx:375
#: src/features/players/components/profile/index.tsx:85 #: src/features/players/components/profile/index.tsx:87
#: src/features/players/components/profile/skeleton.tsx:70 #: src/features/players/components/profile/skeleton.tsx:70
#: src/features/teams/components/team-profile/index.tsx:44 #: src/features/teams/components/team-profile/index.tsx:44
#: src/features/teams/components/team-profile/skeleton.tsx:75 #: src/features/teams/components/team-profile/skeleton.tsx:75
@@ -1621,7 +1621,7 @@ msgstr "Match started!"
msgid "Matches" msgid "Matches"
msgstr "Matches" msgstr "Matches"
#: src/features/matches/components/match-list.tsx:33 #: src/features/matches/components/match-list.tsx:34
msgid "Matches for regionals are unordered" msgid "Matches for regionals are unordered"
msgstr "Matches for regionals are unordered" msgstr "Matches for regionals are unordered"
@@ -1630,11 +1630,11 @@ msgstr "Matches for regionals are unordered"
msgid "Matches Played" msgid "Matches Played"
msgstr "Matches Played" msgstr "Matches Played"
#: src/features/predictions/components/prediction-editor.tsx:249 #: src/features/predictions/components/prediction-editor.tsx:253
msgid "Matchup" msgid "Matchup"
msgstr "Matchup" msgstr "Matchup"
#: src/features/predictions/components/prediction-editor.tsx:226 #: src/features/predictions/components/prediction-editor.tsx:230
msgid "Matchup details" msgid "Matchup details"
msgstr "Matchup details" msgstr "Matchup details"
@@ -1642,20 +1642,20 @@ msgstr "Matchup details"
msgid "MAU" msgid "MAU"
msgstr "MAU" msgstr "MAU"
#: src/features/players/components/league-head-to-head.tsx:123 #: src/features/players/components/league-head-to-head.tsx:128
msgid "Me" msgid "Me"
msgstr "Me" msgstr "Me"
#: src/features/admin/components/dashboard/tools-list.tsx:65 #: src/features/admin/components/dashboard/tools-list.tsx:68
msgid "Migrate Badge Progress" msgid "Migrate Badge Progress"
msgstr "Migrate Badge Progress" msgstr "Migrate Badge Progress"
#: src/features/players/components/player-stats-table.tsx:131 #: src/features/players/components/player-stats-table.tsx:131
#: src/features/players/components/player-stats-table.tsx:356 #: src/features/players/components/player-stats-table.tsx:355
msgid "MMR" msgid "MMR"
msgstr "MMR" msgstr "MMR"
#: src/features/players/components/player-stats-table.tsx:431 #: src/features/players/components/player-stats-table.tsx:430
msgid "MMR Calculation:" msgid "MMR Calculation:"
msgstr "MMR Calculation:" msgstr "MMR Calculation:"
@@ -1712,7 +1712,7 @@ msgstr "New client error"
msgid "New Player" msgid "New Player"
msgstr "New Player" msgstr "New Player"
#: src/features/predictions/components/prediction-editor.tsx:211 #: src/features/predictions/components/prediction-editor.tsx:212
msgid "Next match" msgid "Next match"
msgstr "Next match" msgstr "Next match"
@@ -1775,7 +1775,7 @@ msgstr "No matches scheduled"
msgid "No player activity found" msgid "No player activity found"
msgstr "No player activity found" msgstr "No player activity found"
#: src/features/players/components/league-head-to-head.tsx:151 #: src/features/players/components/league-head-to-head.tsx:156
msgid "No players available" msgid "No players available"
msgstr "No players available" msgstr "No players available"
@@ -1784,8 +1784,8 @@ msgstr "No players available"
msgid "No players enrolled yet" msgid "No players enrolled yet"
msgstr "No players enrolled yet" msgstr "No players enrolled yet"
#: src/features/players/components/league-head-to-head.tsx:151 #: src/features/players/components/league-head-to-head.tsx:156
#: src/features/players/components/player-stats-table.tsx:486 #: src/features/players/components/player-stats-table.tsx:485
msgid "No players found matching \"{search}\"" msgid "No players found matching \"{search}\""
msgstr "No players found matching \"{search}\"" msgstr "No players found matching \"{search}\""
@@ -1818,7 +1818,7 @@ msgstr "No song selected"
msgid "No state captured" msgid "No state captured"
msgstr "No state captured" msgstr "No state captured"
#: src/features/players/components/player-stats-table.tsx:319 #: src/features/players/components/player-stats-table.tsx:318
msgid "No Stats Available" msgid "No Stats Available"
msgstr "No Stats Available" msgstr "No Stats Available"
@@ -1843,7 +1843,7 @@ msgstr "No teams enrolled yet"
msgid "No teams found matching \"{search}\"" msgid "No teams found matching \"{search}\""
msgstr "No teams found matching \"{search}\"" msgstr "No teams found matching \"{search}\""
#: src/features/tournaments/components/tournament-list.tsx:95 #: src/features/tournaments/components/tournament-list.tsx:98
msgid "No tournaments found" msgid "No tournaments found"
msgstr "No tournaments found" msgstr "No tournaments found"
@@ -1908,10 +1908,6 @@ msgstr "Once enrolled, you can view other free agents and their phone number in
msgid "One time code" msgid "One time code"
msgstr "One time code" msgstr "One time code"
#: src/features/admin/components/dashboard/tools-list.tsx:95
msgid "Open Pocketbase"
msgstr "Open Pocketbase"
#: src/features/bracket/components/match-report.tsx:273 #: src/features/bracket/components/match-report.tsx:273
msgid "Opponent" msgid "Opponent"
msgstr "Opponent" msgstr "Opponent"
@@ -1937,7 +1933,7 @@ msgstr "Other players' predictions are hidden until the tournament starts."
msgid "Overtime count is required" msgid "Overtime count is required"
msgstr "Overtime count is required" msgstr "Overtime count is required"
#: src/features/players/components/profile/index.tsx:61 #: src/features/players/components/profile/index.tsx:63
#: src/features/players/components/profile/skeleton.tsx:65 #: src/features/players/components/profile/skeleton.tsx:65
#: src/features/teams/components/team-profile/index.tsx:25 #: src/features/teams/components/team-profile/index.tsx:25
#: src/features/teams/components/team-profile/skeleton.tsx:70 #: src/features/teams/components/team-profile/skeleton.tsx:70
@@ -2047,7 +2043,7 @@ msgstr "Player removed successfully"
msgid "Player Stats" msgid "Player Stats"
msgstr "Player Stats" msgstr "Player Stats"
#: src/app/routes/_authed/admin/telemetry.tsx:48 #: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/features/teams/components/team-profile/index.tsx:29 #: src/features/teams/components/team-profile/index.tsx:29
msgid "Players" msgid "Players"
msgstr "Players" msgstr "Players"
@@ -2083,7 +2079,7 @@ msgstr "Predictions"
msgid "Predictions are open" msgid "Predictions are open"
msgstr "Predictions are open" msgstr "Predictions are open"
#: src/features/predictions/components/prediction-editor.tsx:201 #: src/features/predictions/components/prediction-editor.tsx:202
msgid "Previous match" msgid "Previous match"
msgstr "Previous match" msgstr "Previous match"
@@ -2095,7 +2091,7 @@ msgstr "Previous track"
msgid "Profile" msgid "Profile"
msgstr "Profile" msgstr "Profile"
#: src/features/badges/components/badge-showcase.tsx:301 #: src/features/badges/components/badge-showcase.tsx:302
msgid "Progress " msgid "Progress "
msgstr "Progress " msgstr "Progress "
@@ -2116,13 +2112,13 @@ msgstr "Quest Log"
msgid "Re-roll" msgid "Re-roll"
msgstr "Re-roll" msgstr "Re-roll"
#: src/features/reactions/components/emoji-bar.tsx:213 #: src/features/reactions/components/emoji-bar.tsx:210
msgid "Reactions" msgid "Reactions"
msgstr "Reactions" msgstr "Reactions"
#. placeholder {0}: migrationStatus.playersProcessed #. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers #. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/dashboard/tools-list.tsx:72 #: src/features/admin/components/dashboard/tools-list.tsx:75
msgid "Recalculating badges in the background ({0}/{1})" msgid "Recalculating badges in the background ({0}/{1})"
msgstr "Recalculating badges in the background ({0}/{1})" msgstr "Recalculating badges in the background ({0}/{1})"
@@ -2130,8 +2126,8 @@ msgstr "Recalculating badges in the background ({0}/{1})"
msgid "Recently finished" msgid "Recently finished"
msgstr "Recently finished" msgstr "Recently finished"
#: src/app/routes/_authed/stats.tsx:79 #: src/app/routes/_authed/stats.tsx:82
#: src/features/players/components/profile/index.tsx:37 #: src/features/players/components/profile/index.tsx:39
msgid "Regional" msgid "Regional"
msgstr "Regional" msgstr "Regional"
@@ -2182,7 +2178,7 @@ msgid "rofl"
msgstr "rofl" msgstr "rofl"
#. placeholder {0}: match.round + 1 #. placeholder {0}: match.round + 1
#: src/features/matches/components/match-card.tsx:79 #: src/features/matches/components/match-card.tsx:82
msgid "Round {0}" msgid "Round {0}"
msgstr "Round {0}" msgstr "Round {0}"
@@ -2245,8 +2241,8 @@ msgstr "Search for songs..."
msgid "Search for teams to enroll..." msgid "Search for teams to enroll..."
msgstr "Search for teams to enroll..." msgstr "Search for teams to enroll..."
#: src/features/players/components/league-head-to-head.tsx:132 #: src/features/players/components/league-head-to-head.tsx:137
#: src/features/players/components/player-stats-table.tsx:332 #: src/features/players/components/player-stats-table.tsx:331
msgid "Search players" msgid "Search players"
msgstr "Search players" msgstr "Search players"
@@ -2283,7 +2279,7 @@ msgstr "Select a badge"
msgid "Select a device..." msgid "Select a device..."
msgstr "Select a device..." msgstr "Select a device..."
#: src/features/players/components/league-head-to-head.tsx:112 #: src/features/players/components/league-head-to-head.tsx:117
msgid "Select a player" msgid "Select a player"
msgstr "Select a player" msgstr "Select a player"
@@ -2366,7 +2362,7 @@ msgstr "Server functions"
msgid "serverFn name" msgid "serverFn name"
msgstr "serverFn name" msgstr "serverFn name"
#: src/app/routes/_authed/admin/telemetry.tsx:54 #: src/app/routes/_authed/admin/telemetry.tsx:60
#: src/features/telemetry/components/usage-screen.tsx:212 #: src/features/telemetry/components/usage-screen.tsx:212
msgid "Sessions" msgid "Sessions"
msgstr "Sessions" msgstr "Sessions"
@@ -2382,7 +2378,7 @@ msgstr "Show details"
#. placeholder {0}: filteredAndSortedStats.length #. placeholder {0}: filteredAndSortedStats.length
#. placeholder {1}: playerStats.length #. placeholder {1}: playerStats.length
#: src/features/players/components/player-stats-table.tsx:329 #: src/features/players/components/player-stats-table.tsx:328
msgid "Showing {0} of {1} players" msgid "Showing {0} of {1} players"
msgstr "Showing {0} of {1} players" msgstr "Showing {0} of {1} players"
@@ -2434,7 +2430,7 @@ msgstr "Song segment must be no more than 15 seconds"
msgid "Song start time is required" msgid "Song start time is required"
msgstr "Song start time is required" msgstr "Song start time is required"
#: src/features/players/components/player-stats-table.tsx:350 #: src/features/players/components/player-stats-table.tsx:349
#: src/features/telemetry/components/shared/search-filter-bar.tsx:102 #: src/features/telemetry/components/shared/search-filter-bar.tsx:102
msgid "Sort:" msgid "Sort:"
msgstr "Sort:" msgstr "Sort:"
@@ -2489,21 +2485,21 @@ msgstr "Start typing to search..."
msgid "Starts in" msgid "Starts in"
msgstr "Starts in" msgstr "Starts in"
#: src/features/players/components/player-stats-table.tsx:382 #: src/features/players/components/player-stats-table.tsx:381
msgid "Stat abbreviations and MMR info" msgid "Stat abbreviations and MMR info"
msgstr "Stat abbreviations and MMR info" msgstr "Stat abbreviations and MMR info"
#: src/features/players/components/player-stats-table.tsx:389 #: src/features/players/components/player-stats-table.tsx:388
msgid "Stat Abbreviations:" msgid "Stat Abbreviations:"
msgstr "Stat Abbreviations:" msgstr "Stat Abbreviations:"
#: src/features/core/hooks/use-links.ts:17 #: src/features/core/hooks/use-links.ts:17
#: src/features/players/components/profile/index.tsx:33 #: src/features/players/components/profile/index.tsx:35
#: src/features/teams/components/team-profile/index.tsx:34 #: src/features/teams/components/team-profile/index.tsx:34
msgid "Statistics" msgid "Statistics"
msgstr "Statistics" msgstr "Statistics"
#: src/app/routes/_authed/stats.tsx:52 #: src/app/routes/_authed/stats.tsx:55
msgid "Stats" msgid "Stats"
msgstr "Stats" msgstr "Stats"
@@ -2520,7 +2516,7 @@ msgstr "Status"
#: src/components/sheet/slide-panel/slide-panel.tsx:40 #: src/components/sheet/slide-panel/slide-panel.tsx:40
#: src/features/login/components/player-prompt/index.tsx:115 #: src/features/login/components/player-prompt/index.tsx:115
#: src/features/login/components/player-prompt/index.tsx:126 #: src/features/login/components/player-prompt/index.tsx:126
#: src/features/predictions/components/prediction-editor.tsx:240 #: src/features/predictions/components/prediction-editor.tsx:244
#: src/features/tournaments/components/edit-rules.tsx:39 #: src/features/tournaments/components/edit-rules.tsx:39
msgid "Submit" msgid "Submit"
msgstr "Submit" msgstr "Submit"
@@ -2543,7 +2539,7 @@ msgstr "System"
msgid "T" msgid "T"
msgstr "T" msgstr "T"
#: src/features/players/components/league-head-to-head.tsx:92 #: src/features/players/components/league-head-to-head.tsx:97
msgid "Tap an opponent below to compare" msgid "Tap an opponent below to compare"
msgstr "Tap an opponent below to compare" msgstr "Tap an opponent below to compare"
@@ -2581,7 +2577,7 @@ msgstr "Team Details"
msgid "Team enrolled successfully!" msgid "Team enrolled successfully!"
msgstr "Team enrolled successfully!" msgstr "Team enrolled successfully!"
#: src/app/routes/api/teams/upload-logo.ts:93 #: src/app/routes/api/teams/upload-logo.ts:94
#: src/features/teams/components/team-profile/index.tsx:21 #: src/features/teams/components/team-profile/index.tsx:21
msgid "Team not found" msgid "Team not found"
msgstr "Team not found" msgstr "Team not found"
@@ -2603,7 +2599,7 @@ msgstr "Team updated but logo upload failed: {0}"
msgid "Team updated successfully!" msgid "Team updated successfully!"
msgstr "Team updated successfully!" msgstr "Team updated successfully!"
#: src/features/players/components/profile/index.tsx:90 #: src/features/players/components/profile/index.tsx:92
#: src/features/players/components/profile/skeleton.tsx:75 #: src/features/players/components/profile/skeleton.tsx:75
#: src/features/tournaments/components/profile/index.tsx:41 #: src/features/tournaments/components/profile/index.tsx:41
#: src/features/tournaments/components/profile/skeleton.tsx:89 #: src/features/tournaments/components/profile/skeleton.tsx:89
@@ -2615,8 +2611,8 @@ msgstr "Teams"
msgid "Teams created successfully!" msgid "Teams created successfully!"
msgstr "Teams created successfully!" msgstr "Teams created successfully!"
#: src/app/routes/_authed/admin/telemetry.tsx:20 #: src/app/routes/_authed/admin/telemetry.tsx:26
#: src/features/admin/components/dashboard/tools-list.tsx:55 #: src/features/admin/components/dashboard/tools-list.tsx:57
msgid "Telemetry" msgid "Telemetry"
msgstr "Telemetry" msgstr "Telemetry"
@@ -2730,7 +2726,7 @@ msgstr "Tournament created but logo upload failed: {0}"
msgid "Tournament created successfully!" msgid "Tournament created successfully!"
msgstr "Tournament created successfully!" msgstr "Tournament created successfully!"
#: src/app/routes/api/tournaments/upload-logo.ts:94 #: src/app/routes/api/tournaments/upload-logo.ts:95
msgid "Tournament not found" msgid "Tournament not found"
msgstr "Tournament not found" msgstr "Tournament not found"
@@ -2784,8 +2780,8 @@ msgstr "Unenroll Team"
msgid "Unknown error" msgid "Unknown error"
msgstr "Unknown error" msgstr "Unknown error"
#: src/app/routes/api/teams/upload-logo.ts:142 #: src/app/routes/api/teams/upload-logo.ts:143
#: src/app/routes/api/tournaments/upload-logo.ts:140 #: src/app/routes/api/tournaments/upload-logo.ts:141
msgid "Unknown error occurred" msgid "Unknown error occurred"
msgstr "Unknown error occurred" msgstr "Unknown error occurred"
@@ -2810,7 +2806,7 @@ msgstr "Upcoming"
msgid "Update Match" msgid "Update Match"
msgstr "Update Match" msgstr "Update Match"
#: src/features/players/components/profile/header.tsx:89 #: src/features/players/components/profile/header.tsx:74
msgid "Update Name" msgid "Update Name"
msgstr "Update Name" msgstr "Update Name"
@@ -2824,7 +2820,7 @@ msgstr "Update Team"
msgid "Update Tournament" msgid "Update Tournament"
msgstr "Update Tournament" msgstr "Update Tournament"
#: src/app/routes/_authed/admin/telemetry.tsx:45 #: src/app/routes/_authed/admin/telemetry.tsx:51
msgid "Usage" msgid "Usage"
msgstr "Usage" msgstr "Usage"
@@ -2837,7 +2833,7 @@ msgstr "User Agent"
msgid "versus" msgid "versus"
msgstr "versus" msgstr "versus"
#: src/features/players/components/profile/index.tsx:73 #: src/features/players/components/profile/index.tsx:75
msgid "View all badges" msgid "View all badges"
msgstr "View all badges" msgstr "View all badges"
@@ -2853,8 +2849,8 @@ msgstr "View Bracket"
msgid "View Groups" msgid "View Groups"
msgstr "View Groups" msgstr "View Groups"
#: src/features/bracket/components/match-dock.tsx:182 #: src/features/bracket/components/match-dock.tsx:187
#: src/features/matches/components/match-card.tsx:91 #: src/features/matches/components/match-card.tsx:94
msgid "View head-to-head" msgid "View head-to-head"
msgstr "View head-to-head" msgstr "View head-to-head"
@@ -2935,7 +2931,7 @@ msgstr "Winners Bracket Final"
#: src/components/stats-overview.tsx:103 #: src/components/stats-overview.tsx:103
#: src/components/stats-overview.tsx:136 #: src/components/stats-overview.tsx:136
#: src/features/players/components/player-stats-table.tsx:366 #: src/features/players/components/player-stats-table.tsx:365
msgid "Wins" msgid "Wins"
msgstr "Wins" msgstr "Wins"
+101 -105
View File
@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/features/matches/components/match-card.tsx:80 #: src/features/matches/components/match-card.tsx:83
msgid " (Losers)" msgid " (Losers)"
msgstr " (Perdedores)" msgstr " (Perdedores)"
@@ -93,7 +93,7 @@ msgstr "{0, plural, one {jugador inscrito} other {jugadores inscritos}}"
#. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", }) #. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", })
#. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", }) #. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", })
#: src/features/tournaments/components/started-tournament/header.tsx:51 #: src/features/tournaments/components/started-tournament/header.tsx:56
msgid "{0} at {1}" msgid "{0} at {1}"
msgstr "{0} a las {1}" msgstr "{0} a las {1}"
@@ -228,7 +228,7 @@ msgstr "{namesList} y 1 más"
msgid "{size}-team knockout" msgid "{size}-team knockout"
msgstr "Eliminación directa de {size} equipos" msgstr "Eliminación directa de {size} equipos"
#: src/features/players/components/player-stats-table.tsx:446 #: src/features/players/components/player-stats-table.tsx:445
msgid "* Confidence penalty applied for players with <15 matches" msgid "* Confidence penalty applied for players with <15 matches"
msgstr "* Se aplica una penalización de confianza a jugadores con menos de 15 partidos" msgstr "* Se aplica una penalización de confianza a jugadores con menos de 15 partidos"
@@ -241,7 +241,7 @@ msgstr "* Si es necesario"
msgid "* PICKS shows correct picks / total picks made" msgid "* PICKS shows correct picks / total picks made"
msgstr "* PICKS muestra aciertos / total de pronósticos realizados" msgstr "* PICKS muestra aciertos / total de pronósticos realizados"
#: src/features/players/components/player-stats-table.tsx:449 #: src/features/players/components/player-stats-table.tsx:448
msgid "** Not an official rating" msgid "** Not an official rating"
msgstr "** No es una calificación oficial" msgstr "** No es una calificación oficial"
@@ -254,19 +254,19 @@ msgstr "&"
msgid "#{0}" msgid "#{0}"
msgstr "#{0}" msgstr "#{0}"
#: src/features/players/components/player-stats-table.tsx:416 #: src/features/players/components/player-stats-table.tsx:415
msgid "• <0>AC:</0> Average Cups Per Match" msgid "• <0>AC:</0> Average Cups Per Match"
msgstr "• <0>AC:</0> Promedio de Vasos por Partido" msgstr "• <0>AC:</0> Promedio de Vasos por Partido"
#: src/features/players/components/player-stats-table.tsx:419 #: src/features/players/components/player-stats-table.tsx:418
msgid "• <0>ACA:</0> Average Cups Against" msgid "• <0>ACA:</0> Average Cups Against"
msgstr "• <0>ACA:</0> Promedio de Vasos en Contra" msgstr "• <0>ACA:</0> Promedio de Vasos en Contra"
#: src/features/players/components/player-stats-table.tsx:413 #: src/features/players/components/player-stats-table.tsx:412
msgid "• <0>ALM:</0> Average Loss Margin" msgid "• <0>ALM:</0> Average Loss Margin"
msgstr "• <0>ALM:</0> Margen Promedio de Derrota" msgstr "• <0>ALM:</0> Margen Promedio de Derrota"
#: src/features/players/components/player-stats-table.tsx:410 #: src/features/players/components/player-stats-table.tsx:409
msgid "• <0>AWM:</0> Average Win Margin" msgid "• <0>AWM:</0> Average Win Margin"
msgstr "• <0>AWM:</0> Margen Promedio de Victoria" msgstr "• <0>AWM:</0> Margen Promedio de Victoria"
@@ -274,15 +274,15 @@ msgstr "• <0>AWM:</0> Margen Promedio de Victoria"
msgid "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final" msgid "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final"
msgstr "• <0>Reinicio de bracket:</0> solo se selecciona si tu bracket lo activa — vale el doble de la Final" msgstr "• <0>Reinicio de bracket:</0> solo se selecciona si tu bracket lo activa — vale el doble de la Final"
#: src/features/players/components/player-stats-table.tsx:425 #: src/features/players/components/player-stats-table.tsx:424
msgid "• <0>CA:</0> Cups Against" msgid "• <0>CA:</0> Cups Against"
msgstr "• <0>VC:</0> Vasos en Contra" msgstr "• <0>VC:</0> Vasos en Contra"
#: src/features/players/components/player-stats-table.tsx:422 #: src/features/players/components/player-stats-table.tsx:421
msgid "• <0>CF:</0> Cups For" msgid "• <0>CF:</0> Cups For"
msgstr "• <0>VF:</0> Vasos a Favor" msgstr "• <0>VF:</0> Vasos a Favor"
#: src/features/players/components/player-stats-table.tsx:404 #: src/features/players/components/player-stats-table.tsx:403
msgid "• <0>L:</0> Losses" msgid "• <0>L:</0> Losses"
msgstr "• <0>D:</0> Derrotas" msgstr "• <0>D:</0> Derrotas"
@@ -290,23 +290,23 @@ msgstr "• <0>D:</0> Derrotas"
msgid "• <0>Losers bracket:</0> 5, 10, 20, 40…" msgid "• <0>Losers bracket:</0> 5, 10, 20, 40…"
msgstr "• <0>Bracket de perdedores:</0> 5, 10, 20, 40…" msgstr "• <0>Bracket de perdedores:</0> 5, 10, 20, 40…"
#: src/features/players/components/player-stats-table.tsx:392 #: src/features/players/components/player-stats-table.tsx:391
msgid "• <0>M:</0> Matches" msgid "• <0>M:</0> Matches"
msgstr "• <0>J:</0> Partidos" msgstr "• <0>J:</0> Partidos"
#: src/features/players/components/player-stats-table.tsx:398 #: src/features/players/components/player-stats-table.tsx:397
msgid "• <0>MMR:</0> Matchmaking Rating" msgid "• <0>MMR:</0> Matchmaking Rating"
msgstr "• <0>MMR:</0> Calificación de Emparejamiento" msgstr "• <0>MMR:</0> Calificación de Emparejamiento"
#: src/features/players/components/player-stats-table.tsx:395 #: src/features/players/components/player-stats-table.tsx:394
msgid "• <0>T:</0> Tournaments" msgid "• <0>T:</0> Tournaments"
msgstr "• <0>T:</0> Torneos" msgstr "• <0>T:</0> Torneos"
#: src/features/players/components/player-stats-table.tsx:401 #: src/features/players/components/player-stats-table.tsx:400
msgid "• <0>W:</0> Wins" msgid "• <0>W:</0> Wins"
msgstr "• <0>V:</0> Victorias" msgstr "• <0>V:</0> Victorias"
#: src/features/players/components/player-stats-table.tsx:407 #: src/features/players/components/player-stats-table.tsx:406
msgid "• <0>W%:</0> Win Percentage" msgid "• <0>W%:</0> Win Percentage"
msgstr "• <0>V%:</0> Porcentaje de Victorias" msgstr "• <0>V%:</0> Porcentaje de Victorias"
@@ -314,11 +314,11 @@ msgstr "• <0>V%:</0> Porcentaje de Victorias"
msgid "• <0>Winners bracket:</0> 10, 20, 40, 80…" msgid "• <0>Winners bracket:</0> 10, 20, 40, 80…"
msgstr "• <0>Bracket de ganadores:</0> 10, 20, 40, 80…" msgstr "• <0>Bracket de ganadores:</0> 10, 20, 40, 80…"
#: src/features/players/components/player-stats-table.tsx:437 #: src/features/players/components/player-stats-table.tsx:436
msgid "• Average Cups/Match (25%)" msgid "• Average Cups/Match (25%)"
msgstr "• Promedio de Vasos/Partido (25%)" msgstr "• Promedio de Vasos/Partido (25%)"
#: src/features/players/components/player-stats-table.tsx:440 #: src/features/players/components/player-stats-table.tsx:439
msgid "• Average Win Margin (15%)" msgid "• Average Win Margin (15%)"
msgstr "• Margen Promedio de Victoria (15%)" msgstr "• Margen Promedio de Victoria (15%)"
@@ -326,11 +326,11 @@ msgstr "• Margen Promedio de Victoria (15%)"
msgid "• Each correct pick earns points, doubling every round" msgid "• Each correct pick earns points, doubling every round"
msgstr "• Cada acierto suma puntos, y se duplican cada ronda" msgstr "• Cada acierto suma puntos, y se duplican cada ronda"
#: src/features/players/components/player-stats-table.tsx:443 #: src/features/players/components/player-stats-table.tsx:442
msgid "• Match Volume Bonus (10%)" msgid "• Match Volume Bonus (10%)"
msgstr "• Bono por Volumen de Partidos (10%)" msgstr "• Bono por Volumen de Partidos (10%)"
#: src/features/players/components/player-stats-table.tsx:434 #: src/features/players/components/player-stats-table.tsx:433
msgid "• Win Rate (50%)" msgid "• Win Rate (50%)"
msgstr "• Porcentaje de Victorias (50%)" msgstr "• Porcentaje de Victorias (50%)"
@@ -457,9 +457,9 @@ msgstr "Admin"
msgid "Aero" msgid "Aero"
msgstr "Aero" msgstr "Aero"
#: src/app/routes/_authed/stats.tsx:65 #: src/app/routes/_authed/stats.tsx:68
#: src/features/activities/components/activities-table.tsx:92 #: src/features/activities/components/activities-table.tsx:92
#: src/features/players/components/profile/index.tsx:35 #: src/features/players/components/profile/index.tsx:37
msgid "All" msgid "All"
msgstr "Todos" msgstr "Todos"
@@ -572,7 +572,7 @@ msgstr "Prom. Margen de Victoria"
msgid "Award Badge" msgid "Award Badge"
msgstr "Otorgar insignia" msgstr "Otorgar insignia"
#: src/features/admin/components/dashboard/tools-list.tsx:60 #: src/features/admin/components/dashboard/tools-list.tsx:63
msgid "Award Badges" msgid "Award Badges"
msgstr "Otorgar insignias" msgstr "Otorgar insignias"
@@ -606,15 +606,15 @@ msgstr "Atrás"
msgid "Badge awarded to {playerName}" msgid "Badge awarded to {playerName}"
msgstr "Insignia otorgada a {playerName}" msgstr "Insignia otorgada a {playerName}"
#: src/features/admin/components/dashboard/tools-list.tsx:91 #: src/features/admin/components/dashboard/tools-list.tsx:94
msgid "Badge recalculation failed. Check the server logs." msgid "Badge recalculation failed. Check the server logs."
msgstr "Falló el recálculo de insignias. Revisa los registros del servidor." msgstr "Falló el recálculo de insignias. Revisa los registros del servidor."
#: src/features/admin/components/dashboard/tools-list.tsx:39 #: src/features/admin/components/dashboard/tools-list.tsx:40
msgid "Badge recalculation is already running" msgid "Badge recalculation is already running"
msgstr "El recálculo de insignias ya está en curso" msgstr "El recálculo de insignias ya está en curso"
#: src/features/admin/components/dashboard/tools-list.tsx:38 #: src/features/admin/components/dashboard/tools-list.tsx:39
msgid "Badge recalculation started in the background" msgid "Badge recalculation started in the background"
msgstr "Recálculo de insignias iniciado en segundo plano" msgstr "Recálculo de insignias iniciado en segundo plano"
@@ -622,7 +622,7 @@ msgstr "Recálculo de insignias iniciado en segundo plano"
msgid "Badge Type" msgid "Badge Type"
msgstr "Tipo de insignia" msgstr "Tipo de insignia"
#: src/features/players/components/profile/index.tsx:66 #: src/features/players/components/profile/index.tsx:68
msgid "Badges" msgid "Badges"
msgstr "Insignias" msgstr "Insignias"
@@ -645,7 +645,7 @@ msgid "Bracket"
msgstr "Bracket" msgstr "Bracket"
#: src/app/routes/_authed/admin/preview.tsx:13 #: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/dashboard/tools-list.tsx:102 #: src/features/admin/components/dashboard/tools-list.tsx:98
msgid "Bracket Preview" msgid "Bracket Preview"
msgstr "Vista previa del bracket" msgstr "Vista previa del bracket"
@@ -720,7 +720,7 @@ msgstr "Elige un jugador"
msgid "Claymation" msgid "Claymation"
msgstr "Plastilina" msgstr "Plastilina"
#: src/features/players/components/league-head-to-head.tsx:102 #: src/features/players/components/league-head-to-head.tsx:107
msgid "Clear selected player" msgid "Clear selected player"
msgstr "Quitar jugador seleccionado" msgstr "Quitar jugador seleccionado"
@@ -1118,7 +1118,7 @@ msgstr "Picos de errores y funciones fallando, enviados a esta cuenta"
msgid "Error: {errorMessage}" msgid "Error: {errorMessage}"
msgstr "Error: {errorMessage}" msgstr "Error: {errorMessage}"
#: src/app/routes/_authed/admin/telemetry.tsx:57 #: src/app/routes/_authed/admin/telemetry.tsx:63
#: src/features/telemetry/components/usage-screen.tsx:260 #: src/features/telemetry/components/usage-screen.tsx:260
msgid "Errors" msgid "Errors"
msgstr "Errores" msgstr "Errores"
@@ -1186,7 +1186,7 @@ msgstr "No se pudo establecer el dispositivo activo"
msgid "Failed to set volume" msgid "Failed to set volume"
msgstr "No se pudo ajustar el volumen" msgstr "No se pudo ajustar el volumen"
#: src/features/admin/components/dashboard/tools-list.tsx:42 #: src/features/admin/components/dashboard/tools-list.tsx:43
msgid "Failed to start badge recalculation" msgid "Failed to start badge recalculation"
msgstr "No se pudo iniciar el recálculo de insignias" msgstr "No se pudo iniciar el recálculo de insignias"
@@ -1198,8 +1198,8 @@ msgstr "No se pudo actualizar el equipo"
msgid "Failed to update tournament" msgid "Failed to update tournament"
msgstr "No se pudo actualizar el torneo" msgstr "No se pudo actualizar el torneo"
#: src/app/routes/api/teams/upload-logo.ts:141 #: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:139 #: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/features/teams/components/team-form/index.tsx:148 #: src/features/teams/components/team-form/index.tsx:148
#: src/features/tournaments/components/tournament-form.tsx:88 #: src/features/tournaments/components/tournament-form.tsx:88
msgid "Failed to upload logo" msgid "Failed to upload logo"
@@ -1209,8 +1209,8 @@ msgstr "No se pudo subir el logo"
msgid "Failure" msgid "Failure"
msgstr "Fallo" msgstr "Fallo"
#: src/app/routes/api/teams/upload-logo.ts:80 #: src/app/routes/api/teams/upload-logo.ts:81
#: src/app/routes/api/tournaments/upload-logo.ts:81 #: src/app/routes/api/tournaments/upload-logo.ts:82
msgid "File too large. Maximum size is 10MB." msgid "File too large. Maximum size is 10MB."
msgstr "El archivo es demasiado grande. El tamaño máximo es 10MB." msgstr "El archivo es demasiado grande. El tamaño máximo es 10MB."
@@ -1264,7 +1264,7 @@ msgstr "Función"
msgid "Function Name" msgid "Function Name"
msgstr "Nombre de la función" msgstr "Nombre de la función"
#: src/app/routes/_authed/admin/telemetry.tsx:51 #: src/app/routes/_authed/admin/telemetry.tsx:57
msgid "Functions" msgid "Functions"
msgstr "Funciones" msgstr "Funciones"
@@ -1333,13 +1333,13 @@ msgstr "¡diviértete, mucha suerte!"
msgid "Have you played before?" msgid "Have you played before?"
msgstr "¿Ya has jugado antes?" msgstr "¿Ya has jugado antes?"
#: src/app/routes/_authed/stats.tsx:53 #: src/app/routes/_authed/stats.tsx:56
#: src/features/bracket/components/match-dock.tsx:174 #: src/features/bracket/components/match-dock.tsx:174
#: src/features/bracket/components/match-dock.tsx:219 #: src/features/bracket/components/match-dock.tsx:203
#: src/features/matches/components/match-card.tsx:86 #: src/features/matches/components/match-card.tsx:89
#: src/features/matches/components/match-card.tsx:230 #: src/features/matches/components/match-card.tsx:214
#: src/features/players/components/league-head-to-head.tsx:193 #: src/features/players/components/league-head-to-head.tsx:198
#: src/features/players/components/profile/header.tsx:94 #: src/features/players/components/profile/header.tsx:79
msgid "Head to Head" msgid "Head to Head"
msgstr "Cara a cara" msgstr "Cara a cara"
@@ -1401,8 +1401,8 @@ msgstr "Código incorrecto"
msgid "Invalid action" msgid "Invalid action"
msgstr "Acción no válida" msgstr "Acción no válida"
#: src/app/routes/api/teams/upload-logo.ts:66 #: src/app/routes/api/teams/upload-logo.ts:67
#: src/app/routes/api/tournaments/upload-logo.ts:67 #: src/app/routes/api/tournaments/upload-logo.ts:68
msgid "Invalid file type. Only JPEG, PNG and GIF are allowed." msgid "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgstr "Tipo de archivo no válido. Solo se permiten JPEG, PNG y GIF." msgstr "Tipo de archivo no válido. Solo se permiten JPEG, PNG y GIF."
@@ -1410,8 +1410,8 @@ msgstr "Tipo de archivo no válido. Solo se permiten JPEG, PNG y GIF."
msgid "Invalid hex color format" msgid "Invalid hex color format"
msgstr "Formato de color hexadecimal no válido" msgstr "Formato de color hexadecimal no válido"
#: src/app/routes/api/teams/upload-logo.ts:34 #: src/app/routes/api/teams/upload-logo.ts:35
#: src/app/routes/api/tournaments/upload-logo.ts:35 #: src/app/routes/api/tournaments/upload-logo.ts:36
msgid "Invalid input" msgid "Invalid input"
msgstr "Entrada no válida" msgstr "Entrada no válida"
@@ -1470,7 +1470,7 @@ msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "El apellido debe tener entre 3 y 20 caracteres y contener solo letras" msgstr "El apellido debe tener entre 3 y 20 caracteres y contener solo letras"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned #. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/dashboard/tools-list.tsx:79 #: src/features/admin/components/dashboard/tools-list.tsx:82
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}" msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "Último recálculo: {0, plural, one {# insignia obtenida} other {# insignias obtenidas}}" msgstr "Último recálculo: {0, plural, one {# insignia obtenida} other {# insignias obtenidas}}"
@@ -1504,8 +1504,8 @@ msgstr "La ubicación es obligatoria"
msgid "Logo" msgid "Logo"
msgstr "Logo" msgstr "Logo"
#: src/app/routes/api/teams/upload-logo.ts:47 #: src/app/routes/api/teams/upload-logo.ts:48
#: src/app/routes/api/tournaments/upload-logo.ts:48 #: src/app/routes/api/tournaments/upload-logo.ts:49
msgid "Logo file is required" msgid "Logo file is required"
msgstr "El archivo del logo es obligatorio" msgstr "El archivo del logo es obligatorio"
@@ -1530,8 +1530,8 @@ msgstr "Derrotas"
msgid "M" msgid "M"
msgstr "J" msgstr "J"
#: src/app/routes/_authed/stats.tsx:72 #: src/app/routes/_authed/stats.tsx:75
#: src/features/players/components/profile/index.tsx:36 #: src/features/players/components/profile/index.tsx:38
msgid "Mainline" msgid "Mainline"
msgstr "Mainline" msgstr "Mainline"
@@ -1559,7 +1559,7 @@ msgid "Manage Team Songs/Logos"
msgstr "Administrar canciones/logos del equipo" msgstr "Administrar canciones/logos del equipo"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17 #: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/dashboard/tools-list.tsx:50 #: src/features/admin/components/dashboard/tools-list.tsx:51
msgid "Manage Tournaments" msgid "Manage Tournaments"
msgstr "Administrar torneos" msgstr "Administrar torneos"
@@ -1611,8 +1611,8 @@ msgstr "Historial de partidos ({totalMatches})"
msgid "Match started!" msgid "Match started!"
msgstr "¡Empezó el partido!" msgstr "¡Empezó el partido!"
#: src/features/players/components/player-stats-table.tsx:376 #: src/features/players/components/player-stats-table.tsx:375
#: src/features/players/components/profile/index.tsx:85 #: src/features/players/components/profile/index.tsx:87
#: src/features/players/components/profile/skeleton.tsx:70 #: src/features/players/components/profile/skeleton.tsx:70
#: src/features/teams/components/team-profile/index.tsx:44 #: src/features/teams/components/team-profile/index.tsx:44
#: src/features/teams/components/team-profile/skeleton.tsx:75 #: src/features/teams/components/team-profile/skeleton.tsx:75
@@ -1621,7 +1621,7 @@ msgstr "¡Empezó el partido!"
msgid "Matches" msgid "Matches"
msgstr "Partidos" msgstr "Partidos"
#: src/features/matches/components/match-list.tsx:33 #: src/features/matches/components/match-list.tsx:34
msgid "Matches for regionals are unordered" msgid "Matches for regionals are unordered"
msgstr "Los partidos de regionales no tienen un orden específico" msgstr "Los partidos de regionales no tienen un orden específico"
@@ -1630,11 +1630,11 @@ msgstr "Los partidos de regionales no tienen un orden específico"
msgid "Matches Played" msgid "Matches Played"
msgstr "Partidos jugados" msgstr "Partidos jugados"
#: src/features/predictions/components/prediction-editor.tsx:249 #: src/features/predictions/components/prediction-editor.tsx:253
msgid "Matchup" msgid "Matchup"
msgstr "Enfrentamiento" msgstr "Enfrentamiento"
#: src/features/predictions/components/prediction-editor.tsx:226 #: src/features/predictions/components/prediction-editor.tsx:230
msgid "Matchup details" msgid "Matchup details"
msgstr "Detalles del enfrentamiento" msgstr "Detalles del enfrentamiento"
@@ -1642,20 +1642,20 @@ msgstr "Detalles del enfrentamiento"
msgid "MAU" msgid "MAU"
msgstr "MAU" msgstr "MAU"
#: src/features/players/components/league-head-to-head.tsx:123 #: src/features/players/components/league-head-to-head.tsx:128
msgid "Me" msgid "Me"
msgstr "Yo" msgstr "Yo"
#: src/features/admin/components/dashboard/tools-list.tsx:65 #: src/features/admin/components/dashboard/tools-list.tsx:68
msgid "Migrate Badge Progress" msgid "Migrate Badge Progress"
msgstr "Migrar progreso de insignias" msgstr "Migrar progreso de insignias"
#: src/features/players/components/player-stats-table.tsx:131 #: src/features/players/components/player-stats-table.tsx:131
#: src/features/players/components/player-stats-table.tsx:356 #: src/features/players/components/player-stats-table.tsx:355
msgid "MMR" msgid "MMR"
msgstr "MMR" msgstr "MMR"
#: src/features/players/components/player-stats-table.tsx:431 #: src/features/players/components/player-stats-table.tsx:430
msgid "MMR Calculation:" msgid "MMR Calculation:"
msgstr "Cálculo de MMR:" msgstr "Cálculo de MMR:"
@@ -1712,7 +1712,7 @@ msgstr "Nuevo error de cliente"
msgid "New Player" msgid "New Player"
msgstr "Nuevo jugador" msgstr "Nuevo jugador"
#: src/features/predictions/components/prediction-editor.tsx:211 #: src/features/predictions/components/prediction-editor.tsx:212
msgid "Next match" msgid "Next match"
msgstr "Siguiente partido" msgstr "Siguiente partido"
@@ -1775,7 +1775,7 @@ msgstr "No hay partidos programados"
msgid "No player activity found" msgid "No player activity found"
msgstr "No se encontró actividad de jugadores" msgstr "No se encontró actividad de jugadores"
#: src/features/players/components/league-head-to-head.tsx:151 #: src/features/players/components/league-head-to-head.tsx:156
msgid "No players available" msgid "No players available"
msgstr "No hay jugadores disponibles" msgstr "No hay jugadores disponibles"
@@ -1784,8 +1784,8 @@ msgstr "No hay jugadores disponibles"
msgid "No players enrolled yet" msgid "No players enrolled yet"
msgstr "Aún no hay jugadores inscritos" msgstr "Aún no hay jugadores inscritos"
#: src/features/players/components/league-head-to-head.tsx:151 #: src/features/players/components/league-head-to-head.tsx:156
#: src/features/players/components/player-stats-table.tsx:486 #: src/features/players/components/player-stats-table.tsx:485
msgid "No players found matching \"{search}\"" msgid "No players found matching \"{search}\""
msgstr "No se encontraron jugadores que coincidan con \"{search}\"" msgstr "No se encontraron jugadores que coincidan con \"{search}\""
@@ -1818,7 +1818,7 @@ msgstr "No hay canción seleccionada"
msgid "No state captured" msgid "No state captured"
msgstr "No se capturó ningún estado" msgstr "No se capturó ningún estado"
#: src/features/players/components/player-stats-table.tsx:319 #: src/features/players/components/player-stats-table.tsx:318
msgid "No Stats Available" msgid "No Stats Available"
msgstr "No hay estadísticas disponibles" msgstr "No hay estadísticas disponibles"
@@ -1843,7 +1843,7 @@ msgstr "Aún no hay equipos inscritos"
msgid "No teams found matching \"{search}\"" msgid "No teams found matching \"{search}\""
msgstr "No se encontraron equipos que coincidan con \"{search}\"" msgstr "No se encontraron equipos que coincidan con \"{search}\""
#: src/features/tournaments/components/tournament-list.tsx:95 #: src/features/tournaments/components/tournament-list.tsx:98
msgid "No tournaments found" msgid "No tournaments found"
msgstr "No se encontraron torneos" msgstr "No se encontraron torneos"
@@ -1908,10 +1908,6 @@ msgstr "Una vez inscrito, puedes ver a otros agentes libres y su número de tel
msgid "One time code" msgid "One time code"
msgstr "Código de un solo uso" msgstr "Código de un solo uso"
#: src/features/admin/components/dashboard/tools-list.tsx:95
msgid "Open Pocketbase"
msgstr "Abrir Pocketbase"
#: src/features/bracket/components/match-report.tsx:273 #: src/features/bracket/components/match-report.tsx:273
msgid "Opponent" msgid "Opponent"
msgstr "Rival" msgstr "Rival"
@@ -1937,7 +1933,7 @@ msgstr "Las predicciones de otros jugadores están ocultas hasta que empiece el
msgid "Overtime count is required" msgid "Overtime count is required"
msgstr "La cantidad de tiempos extra es obligatoria" msgstr "La cantidad de tiempos extra es obligatoria"
#: src/features/players/components/profile/index.tsx:61 #: src/features/players/components/profile/index.tsx:63
#: src/features/players/components/profile/skeleton.tsx:65 #: src/features/players/components/profile/skeleton.tsx:65
#: src/features/teams/components/team-profile/index.tsx:25 #: src/features/teams/components/team-profile/index.tsx:25
#: src/features/teams/components/team-profile/skeleton.tsx:70 #: src/features/teams/components/team-profile/skeleton.tsx:70
@@ -2047,7 +2043,7 @@ msgstr "Jugador eliminado con éxito"
msgid "Player Stats" msgid "Player Stats"
msgstr "Estadísticas de jugador" msgstr "Estadísticas de jugador"
#: src/app/routes/_authed/admin/telemetry.tsx:48 #: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/features/teams/components/team-profile/index.tsx:29 #: src/features/teams/components/team-profile/index.tsx:29
msgid "Players" msgid "Players"
msgstr "Jugadores" msgstr "Jugadores"
@@ -2083,7 +2079,7 @@ msgstr "Predicciones"
msgid "Predictions are open" msgid "Predictions are open"
msgstr "Las predicciones están abiertas" msgstr "Las predicciones están abiertas"
#: src/features/predictions/components/prediction-editor.tsx:201 #: src/features/predictions/components/prediction-editor.tsx:202
msgid "Previous match" msgid "Previous match"
msgstr "Partido anterior" msgstr "Partido anterior"
@@ -2095,7 +2091,7 @@ msgstr "Pista anterior"
msgid "Profile" msgid "Profile"
msgstr "Perfil" msgstr "Perfil"
#: src/features/badges/components/badge-showcase.tsx:301 #: src/features/badges/components/badge-showcase.tsx:302
msgid "Progress " msgid "Progress "
msgstr "Progreso " msgstr "Progreso "
@@ -2116,13 +2112,13 @@ msgstr "Misiones"
msgid "Re-roll" msgid "Re-roll"
msgstr "Volver a sortear" msgstr "Volver a sortear"
#: src/features/reactions/components/emoji-bar.tsx:213 #: src/features/reactions/components/emoji-bar.tsx:210
msgid "Reactions" msgid "Reactions"
msgstr "Reacciones" msgstr "Reacciones"
#. placeholder {0}: migrationStatus.playersProcessed #. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers #. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/dashboard/tools-list.tsx:72 #: src/features/admin/components/dashboard/tools-list.tsx:75
msgid "Recalculating badges in the background ({0}/{1})" msgid "Recalculating badges in the background ({0}/{1})"
msgstr "Recalculando insignias en segundo plano ({0}/{1})" msgstr "Recalculando insignias en segundo plano ({0}/{1})"
@@ -2130,8 +2126,8 @@ msgstr "Recalculando insignias en segundo plano ({0}/{1})"
msgid "Recently finished" msgid "Recently finished"
msgstr "Recién terminados" msgstr "Recién terminados"
#: src/app/routes/_authed/stats.tsx:79 #: src/app/routes/_authed/stats.tsx:82
#: src/features/players/components/profile/index.tsx:37 #: src/features/players/components/profile/index.tsx:39
msgid "Regional" msgid "Regional"
msgstr "Regional" msgstr "Regional"
@@ -2182,7 +2178,7 @@ msgid "rofl"
msgstr "carcajada" msgstr "carcajada"
#. placeholder {0}: match.round + 1 #. placeholder {0}: match.round + 1
#: src/features/matches/components/match-card.tsx:79 #: src/features/matches/components/match-card.tsx:82
msgid "Round {0}" msgid "Round {0}"
msgstr "Ronda {0}" msgstr "Ronda {0}"
@@ -2245,8 +2241,8 @@ msgstr "Buscar canciones..."
msgid "Search for teams to enroll..." msgid "Search for teams to enroll..."
msgstr "Buscar equipos para inscribir..." msgstr "Buscar equipos para inscribir..."
#: src/features/players/components/league-head-to-head.tsx:132 #: src/features/players/components/league-head-to-head.tsx:137
#: src/features/players/components/player-stats-table.tsx:332 #: src/features/players/components/player-stats-table.tsx:331
msgid "Search players" msgid "Search players"
msgstr "Buscar jugadores" msgstr "Buscar jugadores"
@@ -2283,7 +2279,7 @@ msgstr "Selecciona una insignia"
msgid "Select a device..." msgid "Select a device..."
msgstr "Selecciona un dispositivo..." msgstr "Selecciona un dispositivo..."
#: src/features/players/components/league-head-to-head.tsx:112 #: src/features/players/components/league-head-to-head.tsx:117
msgid "Select a player" msgid "Select a player"
msgstr "Selecciona un jugador" msgstr "Selecciona un jugador"
@@ -2366,7 +2362,7 @@ msgstr "Funciones del servidor"
msgid "serverFn name" msgid "serverFn name"
msgstr "nombre de serverFn" msgstr "nombre de serverFn"
#: src/app/routes/_authed/admin/telemetry.tsx:54 #: src/app/routes/_authed/admin/telemetry.tsx:60
#: src/features/telemetry/components/usage-screen.tsx:212 #: src/features/telemetry/components/usage-screen.tsx:212
msgid "Sessions" msgid "Sessions"
msgstr "Sesiones" msgstr "Sesiones"
@@ -2382,7 +2378,7 @@ msgstr "Mostrar detalles"
#. placeholder {0}: filteredAndSortedStats.length #. placeholder {0}: filteredAndSortedStats.length
#. placeholder {1}: playerStats.length #. placeholder {1}: playerStats.length
#: src/features/players/components/player-stats-table.tsx:329 #: src/features/players/components/player-stats-table.tsx:328
msgid "Showing {0} of {1} players" msgid "Showing {0} of {1} players"
msgstr "Mostrando {0} de {1} jugadores" msgstr "Mostrando {0} de {1} jugadores"
@@ -2434,7 +2430,7 @@ msgstr "El segmento de la canción no debe durar más de 15 segundos"
msgid "Song start time is required" msgid "Song start time is required"
msgstr "Se requiere la hora de inicio de la canción" msgstr "Se requiere la hora de inicio de la canción"
#: src/features/players/components/player-stats-table.tsx:350 #: src/features/players/components/player-stats-table.tsx:349
#: src/features/telemetry/components/shared/search-filter-bar.tsx:102 #: src/features/telemetry/components/shared/search-filter-bar.tsx:102
msgid "Sort:" msgid "Sort:"
msgstr "Ordenar:" msgstr "Ordenar:"
@@ -2489,21 +2485,21 @@ msgstr "Empieza a escribir para buscar..."
msgid "Starts in" msgid "Starts in"
msgstr "Comienza en" msgstr "Comienza en"
#: src/features/players/components/player-stats-table.tsx:382 #: src/features/players/components/player-stats-table.tsx:381
msgid "Stat abbreviations and MMR info" msgid "Stat abbreviations and MMR info"
msgstr "Abreviaturas de estadísticas e información de MMR" msgstr "Abreviaturas de estadísticas e información de MMR"
#: src/features/players/components/player-stats-table.tsx:389 #: src/features/players/components/player-stats-table.tsx:388
msgid "Stat Abbreviations:" msgid "Stat Abbreviations:"
msgstr "Abreviaturas de estadísticas:" msgstr "Abreviaturas de estadísticas:"
#: src/features/core/hooks/use-links.ts:17 #: src/features/core/hooks/use-links.ts:17
#: src/features/players/components/profile/index.tsx:33 #: src/features/players/components/profile/index.tsx:35
#: src/features/teams/components/team-profile/index.tsx:34 #: src/features/teams/components/team-profile/index.tsx:34
msgid "Statistics" msgid "Statistics"
msgstr "Estadísticas" msgstr "Estadísticas"
#: src/app/routes/_authed/stats.tsx:52 #: src/app/routes/_authed/stats.tsx:55
msgid "Stats" msgid "Stats"
msgstr "Estadísticas" msgstr "Estadísticas"
@@ -2520,7 +2516,7 @@ msgstr "Estado"
#: src/components/sheet/slide-panel/slide-panel.tsx:40 #: src/components/sheet/slide-panel/slide-panel.tsx:40
#: src/features/login/components/player-prompt/index.tsx:115 #: src/features/login/components/player-prompt/index.tsx:115
#: src/features/login/components/player-prompt/index.tsx:126 #: src/features/login/components/player-prompt/index.tsx:126
#: src/features/predictions/components/prediction-editor.tsx:240 #: src/features/predictions/components/prediction-editor.tsx:244
#: src/features/tournaments/components/edit-rules.tsx:39 #: src/features/tournaments/components/edit-rules.tsx:39
msgid "Submit" msgid "Submit"
msgstr "Enviar" msgstr "Enviar"
@@ -2543,7 +2539,7 @@ msgstr "Sistema"
msgid "T" msgid "T"
msgstr "T" msgstr "T"
#: src/features/players/components/league-head-to-head.tsx:92 #: src/features/players/components/league-head-to-head.tsx:97
msgid "Tap an opponent below to compare" msgid "Tap an opponent below to compare"
msgstr "Toca un oponente abajo para comparar" msgstr "Toca un oponente abajo para comparar"
@@ -2581,7 +2577,7 @@ msgstr "Detalles del equipo"
msgid "Team enrolled successfully!" msgid "Team enrolled successfully!"
msgstr "¡Equipo inscrito exitosamente!" msgstr "¡Equipo inscrito exitosamente!"
#: src/app/routes/api/teams/upload-logo.ts:93 #: src/app/routes/api/teams/upload-logo.ts:94
#: src/features/teams/components/team-profile/index.tsx:21 #: src/features/teams/components/team-profile/index.tsx:21
msgid "Team not found" msgid "Team not found"
msgstr "Equipo no encontrado" msgstr "Equipo no encontrado"
@@ -2603,7 +2599,7 @@ msgstr "Equipo actualizado, pero falló la carga del logo: {0}"
msgid "Team updated successfully!" msgid "Team updated successfully!"
msgstr "¡Equipo actualizado exitosamente!" msgstr "¡Equipo actualizado exitosamente!"
#: src/features/players/components/profile/index.tsx:90 #: src/features/players/components/profile/index.tsx:92
#: src/features/players/components/profile/skeleton.tsx:75 #: src/features/players/components/profile/skeleton.tsx:75
#: src/features/tournaments/components/profile/index.tsx:41 #: src/features/tournaments/components/profile/index.tsx:41
#: src/features/tournaments/components/profile/skeleton.tsx:89 #: src/features/tournaments/components/profile/skeleton.tsx:89
@@ -2615,8 +2611,8 @@ msgstr "Equipos"
msgid "Teams created successfully!" msgid "Teams created successfully!"
msgstr "¡Equipos creados exitosamente!" msgstr "¡Equipos creados exitosamente!"
#: src/app/routes/_authed/admin/telemetry.tsx:20 #: src/app/routes/_authed/admin/telemetry.tsx:26
#: src/features/admin/components/dashboard/tools-list.tsx:55 #: src/features/admin/components/dashboard/tools-list.tsx:57
msgid "Telemetry" msgid "Telemetry"
msgstr "Telemetría" msgstr "Telemetría"
@@ -2730,7 +2726,7 @@ msgstr "Torneo creado, pero falló la carga del logo: {0}"
msgid "Tournament created successfully!" msgid "Tournament created successfully!"
msgstr "¡Torneo creado exitosamente!" msgstr "¡Torneo creado exitosamente!"
#: src/app/routes/api/tournaments/upload-logo.ts:94 #: src/app/routes/api/tournaments/upload-logo.ts:95
msgid "Tournament not found" msgid "Tournament not found"
msgstr "Torneo no encontrado" msgstr "Torneo no encontrado"
@@ -2784,8 +2780,8 @@ msgstr "Cancelar inscripción del equipo"
msgid "Unknown error" msgid "Unknown error"
msgstr "Error desconocido" msgstr "Error desconocido"
#: src/app/routes/api/teams/upload-logo.ts:142 #: src/app/routes/api/teams/upload-logo.ts:143
#: src/app/routes/api/tournaments/upload-logo.ts:140 #: src/app/routes/api/tournaments/upload-logo.ts:141
msgid "Unknown error occurred" msgid "Unknown error occurred"
msgstr "Ocurrió un error desconocido" msgstr "Ocurrió un error desconocido"
@@ -2810,7 +2806,7 @@ msgstr "Próximos"
msgid "Update Match" msgid "Update Match"
msgstr "Actualizar partido" msgstr "Actualizar partido"
#: src/features/players/components/profile/header.tsx:89 #: src/features/players/components/profile/header.tsx:74
msgid "Update Name" msgid "Update Name"
msgstr "Actualizar nombre" msgstr "Actualizar nombre"
@@ -2824,7 +2820,7 @@ msgstr "Actualizar equipo"
msgid "Update Tournament" msgid "Update Tournament"
msgstr "Actualizar torneo" msgstr "Actualizar torneo"
#: src/app/routes/_authed/admin/telemetry.tsx:45 #: src/app/routes/_authed/admin/telemetry.tsx:51
msgid "Usage" msgid "Usage"
msgstr "Uso" msgstr "Uso"
@@ -2837,7 +2833,7 @@ msgstr "User Agent"
msgid "versus" msgid "versus"
msgstr "contra" msgstr "contra"
#: src/features/players/components/profile/index.tsx:73 #: src/features/players/components/profile/index.tsx:75
msgid "View all badges" msgid "View all badges"
msgstr "Ver todas las insignias" msgstr "Ver todas las insignias"
@@ -2853,8 +2849,8 @@ msgstr "Ver bracket"
msgid "View Groups" msgid "View Groups"
msgstr "Ver grupos" msgstr "Ver grupos"
#: src/features/bracket/components/match-dock.tsx:182 #: src/features/bracket/components/match-dock.tsx:187
#: src/features/matches/components/match-card.tsx:91 #: src/features/matches/components/match-card.tsx:94
msgid "View head-to-head" msgid "View head-to-head"
msgstr "Ver cara a cara" msgstr "Ver cara a cara"
@@ -2935,7 +2931,7 @@ msgstr "Final del bracket de ganadores"
#: src/components/stats-overview.tsx:103 #: src/components/stats-overview.tsx:103
#: src/components/stats-overview.tsx:136 #: src/components/stats-overview.tsx:136
#: src/features/players/components/player-stats-table.tsx:366 #: src/features/players/components/player-stats-table.tsx:365
msgid "Wins" msgid "Wins"
msgstr "Victorias" msgstr "Victorias"
+101 -105
View File
@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/features/matches/components/match-card.tsx:80 #: src/features/matches/components/match-card.tsx:83
msgid " (Losers)" msgid " (Losers)"
msgstr " (敗者側)" msgstr " (敗者側)"
@@ -93,7 +93,7 @@ msgstr "{0, plural, one {人のプレイヤーが登録済み} other {人のプ
#. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", }) #. placeholder {0}: tournamentStart.toLocaleDateString(i18n.locale, { weekday: "short", month: "short", day: "numeric", })
#. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", }) #. placeholder {1}: tournamentStart.toLocaleTimeString(i18n.locale, { hour: "2-digit", minute: "2-digit", })
#: src/features/tournaments/components/started-tournament/header.tsx:51 #: src/features/tournaments/components/started-tournament/header.tsx:56
msgid "{0} at {1}" msgid "{0} at {1}"
msgstr "{0} {1}" msgstr "{0} {1}"
@@ -228,7 +228,7 @@ msgstr "{namesList}他1名"
msgid "{size}-team knockout" msgid "{size}-team knockout"
msgstr "{size}チームノックアウト" msgstr "{size}チームノックアウト"
#: src/features/players/components/player-stats-table.tsx:446 #: src/features/players/components/player-stats-table.tsx:445
msgid "* Confidence penalty applied for players with <15 matches" msgid "* Confidence penalty applied for players with <15 matches"
msgstr "※試合数が15未満のプレイヤーには信頼度ペナルティが適用されます" msgstr "※試合数が15未満のプレイヤーには信頼度ペナルティが適用されます"
@@ -241,7 +241,7 @@ msgstr "※必要な場合"
msgid "* PICKS shows correct picks / total picks made" msgid "* PICKS shows correct picks / total picks made"
msgstr "※PICKSは正解数/予想総数を表示" msgstr "※PICKSは正解数/予想総数を表示"
#: src/features/players/components/player-stats-table.tsx:449 #: src/features/players/components/player-stats-table.tsx:448
msgid "** Not an official rating" msgid "** Not an official rating"
msgstr "※※公式レーティングではありません" msgstr "※※公式レーティングではありません"
@@ -254,19 +254,19 @@ msgstr "&"
msgid "#{0}" msgid "#{0}"
msgstr "#{0}" msgstr "#{0}"
#: src/features/players/components/player-stats-table.tsx:416 #: src/features/players/components/player-stats-table.tsx:415
msgid "• <0>AC:</0> Average Cups Per Match" msgid "• <0>AC:</0> Average Cups Per Match"
msgstr "• <0>AC:</0>平均獲得カップ数(1試合あたり)" msgstr "• <0>AC:</0>平均獲得カップ数(1試合あたり)"
#: src/features/players/components/player-stats-table.tsx:419 #: src/features/players/components/player-stats-table.tsx:418
msgid "• <0>ACA:</0> Average Cups Against" msgid "• <0>ACA:</0> Average Cups Against"
msgstr "• <0>ACA:</0>平均失カップ数" msgstr "• <0>ACA:</0>平均失カップ数"
#: src/features/players/components/player-stats-table.tsx:413 #: src/features/players/components/player-stats-table.tsx:412
msgid "• <0>ALM:</0> Average Loss Margin" msgid "• <0>ALM:</0> Average Loss Margin"
msgstr "• <0>ALM:</0>平均敗差" msgstr "• <0>ALM:</0>平均敗差"
#: src/features/players/components/player-stats-table.tsx:410 #: src/features/players/components/player-stats-table.tsx:409
msgid "• <0>AWM:</0> Average Win Margin" msgid "• <0>AWM:</0> Average Win Margin"
msgstr "• <0>AWM:</0>平均勝差" msgstr "• <0>AWM:</0>平均勝差"
@@ -274,15 +274,15 @@ msgstr "• <0>AWM:</0>平均勝差"
msgid "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final" msgid "• <0>Bracket reset:</0> only picked if your bracket triggers it — worth double the Final"
msgstr "• <0>ブラケットリセット:</0>自分のブラケットで発生した場合のみ選択可能 — ファイナルの2倍の得点" msgstr "• <0>ブラケットリセット:</0>自分のブラケットで発生した場合のみ選択可能 — ファイナルの2倍の得点"
#: src/features/players/components/player-stats-table.tsx:425 #: src/features/players/components/player-stats-table.tsx:424
msgid "• <0>CA:</0> Cups Against" msgid "• <0>CA:</0> Cups Against"
msgstr "• <0>CA:</0>失カップ数" msgstr "• <0>CA:</0>失カップ数"
#: src/features/players/components/player-stats-table.tsx:422 #: src/features/players/components/player-stats-table.tsx:421
msgid "• <0>CF:</0> Cups For" msgid "• <0>CF:</0> Cups For"
msgstr "• <0>CF:</0>獲得カップ数" msgstr "• <0>CF:</0>獲得カップ数"
#: src/features/players/components/player-stats-table.tsx:404 #: src/features/players/components/player-stats-table.tsx:403
msgid "• <0>L:</0> Losses" msgid "• <0>L:</0> Losses"
msgstr "• <0>L:</0>敗数" msgstr "• <0>L:</0>敗数"
@@ -290,23 +290,23 @@ msgstr "• <0>L:</0>敗数"
msgid "• <0>Losers bracket:</0> 5, 10, 20, 40…" msgid "• <0>Losers bracket:</0> 5, 10, 20, 40…"
msgstr "• <0>敗者ブラケット:</0>5、10、20、40…" msgstr "• <0>敗者ブラケット:</0>5、10、20、40…"
#: src/features/players/components/player-stats-table.tsx:392 #: src/features/players/components/player-stats-table.tsx:391
msgid "• <0>M:</0> Matches" msgid "• <0>M:</0> Matches"
msgstr "• <0>M:</0>試合数" msgstr "• <0>M:</0>試合数"
#: src/features/players/components/player-stats-table.tsx:398 #: src/features/players/components/player-stats-table.tsx:397
msgid "• <0>MMR:</0> Matchmaking Rating" msgid "• <0>MMR:</0> Matchmaking Rating"
msgstr "• <0>MMR:</0>マッチメイキングレーティング" msgstr "• <0>MMR:</0>マッチメイキングレーティング"
#: src/features/players/components/player-stats-table.tsx:395 #: src/features/players/components/player-stats-table.tsx:394
msgid "• <0>T:</0> Tournaments" msgid "• <0>T:</0> Tournaments"
msgstr "• <0>T:</0>トーナメント数" msgstr "• <0>T:</0>トーナメント数"
#: src/features/players/components/player-stats-table.tsx:401 #: src/features/players/components/player-stats-table.tsx:400
msgid "• <0>W:</0> Wins" msgid "• <0>W:</0> Wins"
msgstr "• <0>W:</0>勝数" msgstr "• <0>W:</0>勝数"
#: src/features/players/components/player-stats-table.tsx:407 #: src/features/players/components/player-stats-table.tsx:406
msgid "• <0>W%:</0> Win Percentage" msgid "• <0>W%:</0> Win Percentage"
msgstr "• <0>W%:</0>勝率" msgstr "• <0>W%:</0>勝率"
@@ -314,11 +314,11 @@ msgstr "• <0>W%:</0>勝率"
msgid "• <0>Winners bracket:</0> 10, 20, 40, 80…" msgid "• <0>Winners bracket:</0> 10, 20, 40, 80…"
msgstr "• <0>勝者ブラケット:</0>10、20、40、80…" msgstr "• <0>勝者ブラケット:</0>10、20、40、80…"
#: src/features/players/components/player-stats-table.tsx:437 #: src/features/players/components/player-stats-table.tsx:436
msgid "• Average Cups/Match (25%)" msgid "• Average Cups/Match (25%)"
msgstr "• 平均獲得カップ数/試合(25%)" msgstr "• 平均獲得カップ数/試合(25%)"
#: src/features/players/components/player-stats-table.tsx:440 #: src/features/players/components/player-stats-table.tsx:439
msgid "• Average Win Margin (15%)" msgid "• Average Win Margin (15%)"
msgstr "• 平均勝差(15%)" msgstr "• 平均勝差(15%)"
@@ -326,11 +326,11 @@ msgstr "• 平均勝差(15%)"
msgid "• Each correct pick earns points, doubling every round" msgid "• Each correct pick earns points, doubling every round"
msgstr "• 予想が的中するたびにポイント獲得、ラウンドごとに倍増" msgstr "• 予想が的中するたびにポイント獲得、ラウンドごとに倍増"
#: src/features/players/components/player-stats-table.tsx:443 #: src/features/players/components/player-stats-table.tsx:442
msgid "• Match Volume Bonus (10%)" msgid "• Match Volume Bonus (10%)"
msgstr "• 試合数ボーナス(10%)" msgstr "• 試合数ボーナス(10%)"
#: src/features/players/components/player-stats-table.tsx:434 #: src/features/players/components/player-stats-table.tsx:433
msgid "• Win Rate (50%)" msgid "• Win Rate (50%)"
msgstr "• 勝率(50%)" msgstr "• 勝率(50%)"
@@ -457,9 +457,9 @@ msgstr "管理者"
msgid "Aero" msgid "Aero"
msgstr "エアロ" msgstr "エアロ"
#: src/app/routes/_authed/stats.tsx:65 #: src/app/routes/_authed/stats.tsx:68
#: src/features/activities/components/activities-table.tsx:92 #: src/features/activities/components/activities-table.tsx:92
#: src/features/players/components/profile/index.tsx:35 #: src/features/players/components/profile/index.tsx:37
msgid "All" msgid "All"
msgstr "すべて" msgstr "すべて"
@@ -572,7 +572,7 @@ msgstr "平均勝差"
msgid "Award Badge" msgid "Award Badge"
msgstr "バッジを付与" msgstr "バッジを付与"
#: src/features/admin/components/dashboard/tools-list.tsx:60 #: src/features/admin/components/dashboard/tools-list.tsx:63
msgid "Award Badges" msgid "Award Badges"
msgstr "バッジを付与" msgstr "バッジを付与"
@@ -606,15 +606,15 @@ msgstr "戻る"
msgid "Badge awarded to {playerName}" msgid "Badge awarded to {playerName}"
msgstr "{playerName}にバッジを付与しました" msgstr "{playerName}にバッジを付与しました"
#: src/features/admin/components/dashboard/tools-list.tsx:91 #: src/features/admin/components/dashboard/tools-list.tsx:94
msgid "Badge recalculation failed. Check the server logs." msgid "Badge recalculation failed. Check the server logs."
msgstr "バッジの再計算に失敗しました。サーバーログを確認してください。" msgstr "バッジの再計算に失敗しました。サーバーログを確認してください。"
#: src/features/admin/components/dashboard/tools-list.tsx:39 #: src/features/admin/components/dashboard/tools-list.tsx:40
msgid "Badge recalculation is already running" msgid "Badge recalculation is already running"
msgstr "バッジの再計算はすでに実行中です" msgstr "バッジの再計算はすでに実行中です"
#: src/features/admin/components/dashboard/tools-list.tsx:38 #: src/features/admin/components/dashboard/tools-list.tsx:39
msgid "Badge recalculation started in the background" msgid "Badge recalculation started in the background"
msgstr "バックグラウンドでバッジの再計算を開始しました" msgstr "バックグラウンドでバッジの再計算を開始しました"
@@ -622,7 +622,7 @@ msgstr "バックグラウンドでバッジの再計算を開始しました"
msgid "Badge Type" msgid "Badge Type"
msgstr "バッジの種類" msgstr "バッジの種類"
#: src/features/players/components/profile/index.tsx:66 #: src/features/players/components/profile/index.tsx:68
msgid "Badges" msgid "Badges"
msgstr "バッジ" msgstr "バッジ"
@@ -645,7 +645,7 @@ msgid "Bracket"
msgstr "ブラケット" msgstr "ブラケット"
#: src/app/routes/_authed/admin/preview.tsx:13 #: src/app/routes/_authed/admin/preview.tsx:13
#: src/features/admin/components/dashboard/tools-list.tsx:102 #: src/features/admin/components/dashboard/tools-list.tsx:98
msgid "Bracket Preview" msgid "Bracket Preview"
msgstr "ブラケットプレビュー" msgstr "ブラケットプレビュー"
@@ -720,7 +720,7 @@ msgstr "プレイヤーを選択"
msgid "Claymation" msgid "Claymation"
msgstr "クレイ" msgstr "クレイ"
#: src/features/players/components/league-head-to-head.tsx:102 #: src/features/players/components/league-head-to-head.tsx:107
msgid "Clear selected player" msgid "Clear selected player"
msgstr "選択したプレイヤーを解除" msgstr "選択したプレイヤーを解除"
@@ -1118,7 +1118,7 @@ msgstr "エラーの急増や失敗している関数をこのアカウントに
msgid "Error: {errorMessage}" msgid "Error: {errorMessage}"
msgstr "エラー: {errorMessage}" msgstr "エラー: {errorMessage}"
#: src/app/routes/_authed/admin/telemetry.tsx:57 #: src/app/routes/_authed/admin/telemetry.tsx:63
#: src/features/telemetry/components/usage-screen.tsx:260 #: src/features/telemetry/components/usage-screen.tsx:260
msgid "Errors" msgid "Errors"
msgstr "エラー" msgstr "エラー"
@@ -1186,7 +1186,7 @@ msgstr "アクティブなデバイスの設定に失敗しました"
msgid "Failed to set volume" msgid "Failed to set volume"
msgstr "音量の設定に失敗しました" msgstr "音量の設定に失敗しました"
#: src/features/admin/components/dashboard/tools-list.tsx:42 #: src/features/admin/components/dashboard/tools-list.tsx:43
msgid "Failed to start badge recalculation" msgid "Failed to start badge recalculation"
msgstr "バッジの再計算を開始できませんでした" msgstr "バッジの再計算を開始できませんでした"
@@ -1198,8 +1198,8 @@ msgstr "チームの更新に失敗しました"
msgid "Failed to update tournament" msgid "Failed to update tournament"
msgstr "トーナメントの更新に失敗しました" msgstr "トーナメントの更新に失敗しました"
#: src/app/routes/api/teams/upload-logo.ts:141 #: src/app/routes/api/teams/upload-logo.ts:142
#: src/app/routes/api/tournaments/upload-logo.ts:139 #: src/app/routes/api/tournaments/upload-logo.ts:140
#: src/features/teams/components/team-form/index.tsx:148 #: src/features/teams/components/team-form/index.tsx:148
#: src/features/tournaments/components/tournament-form.tsx:88 #: src/features/tournaments/components/tournament-form.tsx:88
msgid "Failed to upload logo" msgid "Failed to upload logo"
@@ -1209,8 +1209,8 @@ msgstr "ロゴのアップロードに失敗しました"
msgid "Failure" msgid "Failure"
msgstr "失敗" msgstr "失敗"
#: src/app/routes/api/teams/upload-logo.ts:80 #: src/app/routes/api/teams/upload-logo.ts:81
#: src/app/routes/api/tournaments/upload-logo.ts:81 #: src/app/routes/api/tournaments/upload-logo.ts:82
msgid "File too large. Maximum size is 10MB." msgid "File too large. Maximum size is 10MB."
msgstr "ファイルサイズが大きすぎます。最大サイズは10MBです。" msgstr "ファイルサイズが大きすぎます。最大サイズは10MBです。"
@@ -1264,7 +1264,7 @@ msgstr "関数"
msgid "Function Name" msgid "Function Name"
msgstr "関数名" msgstr "関数名"
#: src/app/routes/_authed/admin/telemetry.tsx:51 #: src/app/routes/_authed/admin/telemetry.tsx:57
msgid "Functions" msgid "Functions"
msgstr "関数" msgstr "関数"
@@ -1333,13 +1333,13 @@ msgstr "楽しんで、頑張ってください!"
msgid "Have you played before?" msgid "Have you played before?"
msgstr "参加したことはありますか?" msgstr "参加したことはありますか?"
#: src/app/routes/_authed/stats.tsx:53 #: src/app/routes/_authed/stats.tsx:56
#: src/features/bracket/components/match-dock.tsx:174 #: src/features/bracket/components/match-dock.tsx:174
#: src/features/bracket/components/match-dock.tsx:219 #: src/features/bracket/components/match-dock.tsx:203
#: src/features/matches/components/match-card.tsx:86 #: src/features/matches/components/match-card.tsx:89
#: src/features/matches/components/match-card.tsx:230 #: src/features/matches/components/match-card.tsx:214
#: src/features/players/components/league-head-to-head.tsx:193 #: src/features/players/components/league-head-to-head.tsx:198
#: src/features/players/components/profile/header.tsx:94 #: src/features/players/components/profile/header.tsx:79
msgid "Head to Head" msgid "Head to Head"
msgstr "対戦成績" msgstr "対戦成績"
@@ -1401,8 +1401,8 @@ msgstr "コードが正しくありません"
msgid "Invalid action" msgid "Invalid action"
msgstr "無効な操作です" msgstr "無効な操作です"
#: src/app/routes/api/teams/upload-logo.ts:66 #: src/app/routes/api/teams/upload-logo.ts:67
#: src/app/routes/api/tournaments/upload-logo.ts:67 #: src/app/routes/api/tournaments/upload-logo.ts:68
msgid "Invalid file type. Only JPEG, PNG and GIF are allowed." msgid "Invalid file type. Only JPEG, PNG and GIF are allowed."
msgstr "無効なファイル形式です。JPEG、PNG、GIFのみ使用できます。" msgstr "無効なファイル形式です。JPEG、PNG、GIFのみ使用できます。"
@@ -1410,8 +1410,8 @@ msgstr "無効なファイル形式です。JPEG、PNG、GIFのみ使用でき
msgid "Invalid hex color format" msgid "Invalid hex color format"
msgstr "無効なHEXカラー形式です" msgstr "無効なHEXカラー形式です"
#: src/app/routes/api/teams/upload-logo.ts:34 #: src/app/routes/api/teams/upload-logo.ts:35
#: src/app/routes/api/tournaments/upload-logo.ts:35 #: src/app/routes/api/tournaments/upload-logo.ts:36
msgid "Invalid input" msgid "Invalid input"
msgstr "無効な入力です" msgstr "無効な入力です"
@@ -1470,7 +1470,7 @@ msgid "Last name must be 3-20 characters long and contain only letters"
msgstr "姓は3〜20文字の英字のみで入力してください" msgstr "姓は3〜20文字の英字のみで入力してください"
#. placeholder {0}: migrationStatus.result.totalBadgesEarned #. placeholder {0}: migrationStatus.result.totalBadgesEarned
#: src/features/admin/components/dashboard/tools-list.tsx:79 #: src/features/admin/components/dashboard/tools-list.tsx:82
msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}" msgid "Last recalculation: {0, plural, one {# badge earned} other {# badges earned}}"
msgstr "前回の再計算:{0, plural, one {# 個のバッジを獲得} other {# 個のバッジを獲得}}" msgstr "前回の再計算:{0, plural, one {# 個のバッジを獲得} other {# 個のバッジを獲得}}"
@@ -1504,8 +1504,8 @@ msgstr "場所は必須です"
msgid "Logo" msgid "Logo"
msgstr "ロゴ" msgstr "ロゴ"
#: src/app/routes/api/teams/upload-logo.ts:47 #: src/app/routes/api/teams/upload-logo.ts:48
#: src/app/routes/api/tournaments/upload-logo.ts:48 #: src/app/routes/api/tournaments/upload-logo.ts:49
msgid "Logo file is required" msgid "Logo file is required"
msgstr "ロゴファイルは必須です" msgstr "ロゴファイルは必須です"
@@ -1530,8 +1530,8 @@ msgstr "敗数"
msgid "M" msgid "M"
msgstr "M" msgstr "M"
#: src/app/routes/_authed/stats.tsx:72 #: src/app/routes/_authed/stats.tsx:75
#: src/features/players/components/profile/index.tsx:36 #: src/features/players/components/profile/index.tsx:38
msgid "Mainline" msgid "Mainline"
msgstr "メインライン" msgstr "メインライン"
@@ -1559,7 +1559,7 @@ msgid "Manage Team Songs/Logos"
msgstr "チームの曲・ロゴを管理" msgstr "チームの曲・ロゴを管理"
#: src/app/routes/_authed/admin/tournaments/index.tsx:17 #: src/app/routes/_authed/admin/tournaments/index.tsx:17
#: src/features/admin/components/dashboard/tools-list.tsx:50 #: src/features/admin/components/dashboard/tools-list.tsx:51
msgid "Manage Tournaments" msgid "Manage Tournaments"
msgstr "トーナメント管理" msgstr "トーナメント管理"
@@ -1611,8 +1611,8 @@ msgstr "対戦履歴({totalMatches})"
msgid "Match started!" msgid "Match started!"
msgstr "試合開始!" msgstr "試合開始!"
#: src/features/players/components/player-stats-table.tsx:376 #: src/features/players/components/player-stats-table.tsx:375
#: src/features/players/components/profile/index.tsx:85 #: src/features/players/components/profile/index.tsx:87
#: src/features/players/components/profile/skeleton.tsx:70 #: src/features/players/components/profile/skeleton.tsx:70
#: src/features/teams/components/team-profile/index.tsx:44 #: src/features/teams/components/team-profile/index.tsx:44
#: src/features/teams/components/team-profile/skeleton.tsx:75 #: src/features/teams/components/team-profile/skeleton.tsx:75
@@ -1621,7 +1621,7 @@ msgstr "試合開始!"
msgid "Matches" msgid "Matches"
msgstr "試合" msgstr "試合"
#: src/features/matches/components/match-list.tsx:33 #: src/features/matches/components/match-list.tsx:34
msgid "Matches for regionals are unordered" msgid "Matches for regionals are unordered"
msgstr "地区予選の試合は順不同です" msgstr "地区予選の試合は順不同です"
@@ -1630,11 +1630,11 @@ msgstr "地区予選の試合は順不同です"
msgid "Matches Played" msgid "Matches Played"
msgstr "試合数" msgstr "試合数"
#: src/features/predictions/components/prediction-editor.tsx:249 #: src/features/predictions/components/prediction-editor.tsx:253
msgid "Matchup" msgid "Matchup"
msgstr "対戦カード" msgstr "対戦カード"
#: src/features/predictions/components/prediction-editor.tsx:226 #: src/features/predictions/components/prediction-editor.tsx:230
msgid "Matchup details" msgid "Matchup details"
msgstr "対戦カードの詳細" msgstr "対戦カードの詳細"
@@ -1642,20 +1642,20 @@ msgstr "対戦カードの詳細"
msgid "MAU" msgid "MAU"
msgstr "MAU" msgstr "MAU"
#: src/features/players/components/league-head-to-head.tsx:123 #: src/features/players/components/league-head-to-head.tsx:128
msgid "Me" msgid "Me"
msgstr "自分" msgstr "自分"
#: src/features/admin/components/dashboard/tools-list.tsx:65 #: src/features/admin/components/dashboard/tools-list.tsx:68
msgid "Migrate Badge Progress" msgid "Migrate Badge Progress"
msgstr "バッジ進捗を移行" msgstr "バッジ進捗を移行"
#: src/features/players/components/player-stats-table.tsx:131 #: src/features/players/components/player-stats-table.tsx:131
#: src/features/players/components/player-stats-table.tsx:356 #: src/features/players/components/player-stats-table.tsx:355
msgid "MMR" msgid "MMR"
msgstr "MMR" msgstr "MMR"
#: src/features/players/components/player-stats-table.tsx:431 #: src/features/players/components/player-stats-table.tsx:430
msgid "MMR Calculation:" msgid "MMR Calculation:"
msgstr "MMR計算:" msgstr "MMR計算:"
@@ -1712,7 +1712,7 @@ msgstr "新しいクライアントエラー"
msgid "New Player" msgid "New Player"
msgstr "新しいプレイヤー" msgstr "新しいプレイヤー"
#: src/features/predictions/components/prediction-editor.tsx:211 #: src/features/predictions/components/prediction-editor.tsx:212
msgid "Next match" msgid "Next match"
msgstr "次の試合" msgstr "次の試合"
@@ -1775,7 +1775,7 @@ msgstr "予定されている試合はありません"
msgid "No player activity found" msgid "No player activity found"
msgstr "プレイヤーのアクティビティが見つかりません" msgstr "プレイヤーのアクティビティが見つかりません"
#: src/features/players/components/league-head-to-head.tsx:151 #: src/features/players/components/league-head-to-head.tsx:156
msgid "No players available" msgid "No players available"
msgstr "利用可能なプレイヤーがいません" msgstr "利用可能なプレイヤーがいません"
@@ -1784,8 +1784,8 @@ msgstr "利用可能なプレイヤーがいません"
msgid "No players enrolled yet" msgid "No players enrolled yet"
msgstr "まだプレイヤーが登録されていません" msgstr "まだプレイヤーが登録されていません"
#: src/features/players/components/league-head-to-head.tsx:151 #: src/features/players/components/league-head-to-head.tsx:156
#: src/features/players/components/player-stats-table.tsx:486 #: src/features/players/components/player-stats-table.tsx:485
msgid "No players found matching \"{search}\"" msgid "No players found matching \"{search}\""
msgstr "「{search}」に一致するプレイヤーが見つかりません" msgstr "「{search}」に一致するプレイヤーが見つかりません"
@@ -1818,7 +1818,7 @@ msgstr "曲が選択されていません"
msgid "No state captured" msgid "No state captured"
msgstr "キャプチャされた状態がありません" msgstr "キャプチャされた状態がありません"
#: src/features/players/components/player-stats-table.tsx:319 #: src/features/players/components/player-stats-table.tsx:318
msgid "No Stats Available" msgid "No Stats Available"
msgstr "利用可能な統計がありません" msgstr "利用可能な統計がありません"
@@ -1843,7 +1843,7 @@ msgstr "まだチームが登録されていません"
msgid "No teams found matching \"{search}\"" msgid "No teams found matching \"{search}\""
msgstr "「{search}」に一致するチームが見つかりません" msgstr "「{search}」に一致するチームが見つかりません"
#: src/features/tournaments/components/tournament-list.tsx:95 #: src/features/tournaments/components/tournament-list.tsx:98
msgid "No tournaments found" msgid "No tournaments found"
msgstr "トーナメントが見つかりません" msgstr "トーナメントが見つかりません"
@@ -1908,10 +1908,6 @@ msgstr "登録が完了すると、他のフリーエージェントとその電
msgid "One time code" msgid "One time code"
msgstr "ワンタイムコード" msgstr "ワンタイムコード"
#: src/features/admin/components/dashboard/tools-list.tsx:95
msgid "Open Pocketbase"
msgstr "Pocketbaseを開く"
#: src/features/bracket/components/match-report.tsx:273 #: src/features/bracket/components/match-report.tsx:273
msgid "Opponent" msgid "Opponent"
msgstr "対戦相手" msgstr "対戦相手"
@@ -1937,7 +1933,7 @@ msgstr "トーナメントが始まるまで、他のプレイヤーの予想は
msgid "Overtime count is required" msgid "Overtime count is required"
msgstr "OT回数の入力が必要です" msgstr "OT回数の入力が必要です"
#: src/features/players/components/profile/index.tsx:61 #: src/features/players/components/profile/index.tsx:63
#: src/features/players/components/profile/skeleton.tsx:65 #: src/features/players/components/profile/skeleton.tsx:65
#: src/features/teams/components/team-profile/index.tsx:25 #: src/features/teams/components/team-profile/index.tsx:25
#: src/features/teams/components/team-profile/skeleton.tsx:70 #: src/features/teams/components/team-profile/skeleton.tsx:70
@@ -2047,7 +2043,7 @@ msgstr "プレイヤーを削除しました"
msgid "Player Stats" msgid "Player Stats"
msgstr "プレイヤースタッツ" msgstr "プレイヤースタッツ"
#: src/app/routes/_authed/admin/telemetry.tsx:48 #: src/app/routes/_authed/admin/telemetry.tsx:54
#: src/features/teams/components/team-profile/index.tsx:29 #: src/features/teams/components/team-profile/index.tsx:29
msgid "Players" msgid "Players"
msgstr "プレイヤー" msgstr "プレイヤー"
@@ -2083,7 +2079,7 @@ msgstr "予想"
msgid "Predictions are open" msgid "Predictions are open"
msgstr "予想受付中" msgstr "予想受付中"
#: src/features/predictions/components/prediction-editor.tsx:201 #: src/features/predictions/components/prediction-editor.tsx:202
msgid "Previous match" msgid "Previous match"
msgstr "前の試合" msgstr "前の試合"
@@ -2095,7 +2091,7 @@ msgstr "前のトラック"
msgid "Profile" msgid "Profile"
msgstr "プロフィール" msgstr "プロフィール"
#: src/features/badges/components/badge-showcase.tsx:301 #: src/features/badges/components/badge-showcase.tsx:302
msgid "Progress " msgid "Progress "
msgstr "進行状況 " msgstr "進行状況 "
@@ -2116,13 +2112,13 @@ msgstr "クエスト"
msgid "Re-roll" msgid "Re-roll"
msgstr "再抽選" msgstr "再抽選"
#: src/features/reactions/components/emoji-bar.tsx:213 #: src/features/reactions/components/emoji-bar.tsx:210
msgid "Reactions" msgid "Reactions"
msgstr "リアクション" msgstr "リアクション"
#. placeholder {0}: migrationStatus.playersProcessed #. placeholder {0}: migrationStatus.playersProcessed
#. placeholder {1}: migrationStatus.totalPlayers #. placeholder {1}: migrationStatus.totalPlayers
#: src/features/admin/components/dashboard/tools-list.tsx:72 #: src/features/admin/components/dashboard/tools-list.tsx:75
msgid "Recalculating badges in the background ({0}/{1})" msgid "Recalculating badges in the background ({0}/{1})"
msgstr "バックグラウンドでバッジを再計算中({0}/{1})" msgstr "バックグラウンドでバッジを再計算中({0}/{1})"
@@ -2130,8 +2126,8 @@ msgstr "バックグラウンドでバッジを再計算中({0}/{1})"
msgid "Recently finished" msgid "Recently finished"
msgstr "最近終了した試合" msgstr "最近終了した試合"
#: src/app/routes/_authed/stats.tsx:79 #: src/app/routes/_authed/stats.tsx:82
#: src/features/players/components/profile/index.tsx:37 #: src/features/players/components/profile/index.tsx:39
msgid "Regional" msgid "Regional"
msgstr "地域" msgstr "地域"
@@ -2182,7 +2178,7 @@ msgid "rofl"
msgstr "大笑い" msgstr "大笑い"
#. placeholder {0}: match.round + 1 #. placeholder {0}: match.round + 1
#: src/features/matches/components/match-card.tsx:79 #: src/features/matches/components/match-card.tsx:82
msgid "Round {0}" msgid "Round {0}"
msgstr "ラウンド{0}" msgstr "ラウンド{0}"
@@ -2245,8 +2241,8 @@ msgstr "曲を検索..."
msgid "Search for teams to enroll..." msgid "Search for teams to enroll..."
msgstr "登録するチームを検索..." msgstr "登録するチームを検索..."
#: src/features/players/components/league-head-to-head.tsx:132 #: src/features/players/components/league-head-to-head.tsx:137
#: src/features/players/components/player-stats-table.tsx:332 #: src/features/players/components/player-stats-table.tsx:331
msgid "Search players" msgid "Search players"
msgstr "プレイヤーを検索" msgstr "プレイヤーを検索"
@@ -2283,7 +2279,7 @@ msgstr "バッジを選択"
msgid "Select a device..." msgid "Select a device..."
msgstr "デバイスを選択..." msgstr "デバイスを選択..."
#: src/features/players/components/league-head-to-head.tsx:112 #: src/features/players/components/league-head-to-head.tsx:117
msgid "Select a player" msgid "Select a player"
msgstr "プレイヤーを選択" msgstr "プレイヤーを選択"
@@ -2366,7 +2362,7 @@ msgstr "サーバー関数"
msgid "serverFn name" msgid "serverFn name"
msgstr "serverFn名" msgstr "serverFn名"
#: src/app/routes/_authed/admin/telemetry.tsx:54 #: src/app/routes/_authed/admin/telemetry.tsx:60
#: src/features/telemetry/components/usage-screen.tsx:212 #: src/features/telemetry/components/usage-screen.tsx:212
msgid "Sessions" msgid "Sessions"
msgstr "セッション数" msgstr "セッション数"
@@ -2382,7 +2378,7 @@ msgstr "詳細を表示"
#. placeholder {0}: filteredAndSortedStats.length #. placeholder {0}: filteredAndSortedStats.length
#. placeholder {1}: playerStats.length #. placeholder {1}: playerStats.length
#: src/features/players/components/player-stats-table.tsx:329 #: src/features/players/components/player-stats-table.tsx:328
msgid "Showing {0} of {1} players" msgid "Showing {0} of {1} players"
msgstr "{1}人中{0}人のプレイヤーを表示" msgstr "{1}人中{0}人のプレイヤーを表示"
@@ -2434,7 +2430,7 @@ msgstr "曲のセグメントは15秒以内にしてください"
msgid "Song start time is required" msgid "Song start time is required"
msgstr "曲の開始時間を入力してください" msgstr "曲の開始時間を入力してください"
#: src/features/players/components/player-stats-table.tsx:350 #: src/features/players/components/player-stats-table.tsx:349
#: src/features/telemetry/components/shared/search-filter-bar.tsx:102 #: src/features/telemetry/components/shared/search-filter-bar.tsx:102
msgid "Sort:" msgid "Sort:"
msgstr "並び替え:" msgstr "並び替え:"
@@ -2489,21 +2485,21 @@ msgstr "入力して検索..."
msgid "Starts in" msgid "Starts in"
msgstr "開始まで" msgstr "開始まで"
#: src/features/players/components/player-stats-table.tsx:382 #: src/features/players/components/player-stats-table.tsx:381
msgid "Stat abbreviations and MMR info" msgid "Stat abbreviations and MMR info"
msgstr "スタッツ略語とMMR情報" msgstr "スタッツ略語とMMR情報"
#: src/features/players/components/player-stats-table.tsx:389 #: src/features/players/components/player-stats-table.tsx:388
msgid "Stat Abbreviations:" msgid "Stat Abbreviations:"
msgstr "スタッツ略語:" msgstr "スタッツ略語:"
#: src/features/core/hooks/use-links.ts:17 #: src/features/core/hooks/use-links.ts:17
#: src/features/players/components/profile/index.tsx:33 #: src/features/players/components/profile/index.tsx:35
#: src/features/teams/components/team-profile/index.tsx:34 #: src/features/teams/components/team-profile/index.tsx:34
msgid "Statistics" msgid "Statistics"
msgstr "統計" msgstr "統計"
#: src/app/routes/_authed/stats.tsx:52 #: src/app/routes/_authed/stats.tsx:55
msgid "Stats" msgid "Stats"
msgstr "スタッツ" msgstr "スタッツ"
@@ -2520,7 +2516,7 @@ msgstr "ステータス"
#: src/components/sheet/slide-panel/slide-panel.tsx:40 #: src/components/sheet/slide-panel/slide-panel.tsx:40
#: src/features/login/components/player-prompt/index.tsx:115 #: src/features/login/components/player-prompt/index.tsx:115
#: src/features/login/components/player-prompt/index.tsx:126 #: src/features/login/components/player-prompt/index.tsx:126
#: src/features/predictions/components/prediction-editor.tsx:240 #: src/features/predictions/components/prediction-editor.tsx:244
#: src/features/tournaments/components/edit-rules.tsx:39 #: src/features/tournaments/components/edit-rules.tsx:39
msgid "Submit" msgid "Submit"
msgstr "送信" msgstr "送信"
@@ -2543,7 +2539,7 @@ msgstr "システム"
msgid "T" msgid "T"
msgstr "T" msgstr "T"
#: src/features/players/components/league-head-to-head.tsx:92 #: src/features/players/components/league-head-to-head.tsx:97
msgid "Tap an opponent below to compare" msgid "Tap an opponent below to compare"
msgstr "下の対戦相手をタップして比較" msgstr "下の対戦相手をタップして比較"
@@ -2581,7 +2577,7 @@ msgstr "チーム詳細"
msgid "Team enrolled successfully!" msgid "Team enrolled successfully!"
msgstr "チームを登録しました!" msgstr "チームを登録しました!"
#: src/app/routes/api/teams/upload-logo.ts:93 #: src/app/routes/api/teams/upload-logo.ts:94
#: src/features/teams/components/team-profile/index.tsx:21 #: src/features/teams/components/team-profile/index.tsx:21
msgid "Team not found" msgid "Team not found"
msgstr "チームが見つかりません" msgstr "チームが見つかりません"
@@ -2603,7 +2599,7 @@ msgstr "チームを更新しましたが、ロゴのアップロードに失敗
msgid "Team updated successfully!" msgid "Team updated successfully!"
msgstr "チームを更新しました!" msgstr "チームを更新しました!"
#: src/features/players/components/profile/index.tsx:90 #: src/features/players/components/profile/index.tsx:92
#: src/features/players/components/profile/skeleton.tsx:75 #: src/features/players/components/profile/skeleton.tsx:75
#: src/features/tournaments/components/profile/index.tsx:41 #: src/features/tournaments/components/profile/index.tsx:41
#: src/features/tournaments/components/profile/skeleton.tsx:89 #: src/features/tournaments/components/profile/skeleton.tsx:89
@@ -2615,8 +2611,8 @@ msgstr "チーム"
msgid "Teams created successfully!" msgid "Teams created successfully!"
msgstr "チームを作成しました!" msgstr "チームを作成しました!"
#: src/app/routes/_authed/admin/telemetry.tsx:20 #: src/app/routes/_authed/admin/telemetry.tsx:26
#: src/features/admin/components/dashboard/tools-list.tsx:55 #: src/features/admin/components/dashboard/tools-list.tsx:57
msgid "Telemetry" msgid "Telemetry"
msgstr "テレメトリー" msgstr "テレメトリー"
@@ -2730,7 +2726,7 @@ msgstr "トーナメントを作成しましたが、ロゴのアップロード
msgid "Tournament created successfully!" msgid "Tournament created successfully!"
msgstr "トーナメントを作成しました!" msgstr "トーナメントを作成しました!"
#: src/app/routes/api/tournaments/upload-logo.ts:94 #: src/app/routes/api/tournaments/upload-logo.ts:95
msgid "Tournament not found" msgid "Tournament not found"
msgstr "トーナメントが見つかりません" msgstr "トーナメントが見つかりません"
@@ -2784,8 +2780,8 @@ msgstr "チームを登録解除"
msgid "Unknown error" msgid "Unknown error"
msgstr "不明なエラー" msgstr "不明なエラー"
#: src/app/routes/api/teams/upload-logo.ts:142 #: src/app/routes/api/teams/upload-logo.ts:143
#: src/app/routes/api/tournaments/upload-logo.ts:140 #: src/app/routes/api/tournaments/upload-logo.ts:141
msgid "Unknown error occurred" msgid "Unknown error occurred"
msgstr "不明なエラーが発生しました" msgstr "不明なエラーが発生しました"
@@ -2810,7 +2806,7 @@ msgstr "開催予定"
msgid "Update Match" msgid "Update Match"
msgstr "試合を更新" msgstr "試合を更新"
#: src/features/players/components/profile/header.tsx:89 #: src/features/players/components/profile/header.tsx:74
msgid "Update Name" msgid "Update Name"
msgstr "名前を更新" msgstr "名前を更新"
@@ -2824,7 +2820,7 @@ msgstr "チームを更新"
msgid "Update Tournament" msgid "Update Tournament"
msgstr "トーナメントを更新" msgstr "トーナメントを更新"
#: src/app/routes/_authed/admin/telemetry.tsx:45 #: src/app/routes/_authed/admin/telemetry.tsx:51
msgid "Usage" msgid "Usage"
msgstr "利用状況" msgstr "利用状況"
@@ -2837,7 +2833,7 @@ msgstr "ユーザーエージェント"
msgid "versus" msgid "versus"
msgstr "対" msgstr "対"
#: src/features/players/components/profile/index.tsx:73 #: src/features/players/components/profile/index.tsx:75
msgid "View all badges" msgid "View all badges"
msgstr "すべてのバッジを見る" msgstr "すべてのバッジを見る"
@@ -2853,8 +2849,8 @@ msgstr "ブラケットを見る"
msgid "View Groups" msgid "View Groups"
msgstr "グループを見る" msgstr "グループを見る"
#: src/features/bracket/components/match-dock.tsx:182 #: src/features/bracket/components/match-dock.tsx:187
#: src/features/matches/components/match-card.tsx:91 #: src/features/matches/components/match-card.tsx:94
msgid "View head-to-head" msgid "View head-to-head"
msgstr "対戦成績を見る" msgstr "対戦成績を見る"
@@ -2935,7 +2931,7 @@ msgstr "勝者ブラケット決勝"
#: src/components/stats-overview.tsx:103 #: src/components/stats-overview.tsx:103
#: src/components/stats-overview.tsx:136 #: src/components/stats-overview.tsx:136
#: src/features/players/components/player-stats-table.tsx:366 #: src/features/players/components/player-stats-table.tsx:365
msgid "Wins" msgid "Wins"
msgstr "勝利数" msgstr "勝利数"
+3 -4
View File
@@ -178,15 +178,14 @@
/* 3. Fixed sky backdrop with static bubbles (below all content) */ /* 3. Fixed sky backdrop with static bubbles (below all content) */
/* ---------------------------------------------------------------- */ /* ---------------------------------------------------------------- */
[data-theme="aero"] body::before { [data-theme="aero"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: -1; z-index: -1;
pointer-events: none; pointer-events: none;
} }
[data-theme="aero"][data-mantine-color-scheme="light"] body::before { [data-theme="aero"][data-mantine-color-scheme="light"] .flxn-theme-overlay {
background-color: #e9f6fe; background-color: #e9f6fe;
background-image: background-image:
radial-gradient(circle 0.6rem at 13.5% 20%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 70%), radial-gradient(circle 0.6rem at 13.5% 20%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 70%),
@@ -198,7 +197,7 @@
linear-gradient(180deg, #a5daf6 0%, #c9e9fa 32%, #e9f6fe 60%, #ffffff 100%); linear-gradient(180deg, #a5daf6 0%, #c9e9fa 32%, #e9f6fe 60%, #ffffff 100%);
} }
[data-theme="aero"][data-mantine-color-scheme="dark"] body::before { [data-theme="aero"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
background-color: #071e2d; background-color: #071e2d;
background-image: background-image:
radial-gradient(circle 4rem at 16% 24%, rgba(120, 205, 245, 0) 60%, rgba(120, 205, 245, 0.2) 67%, rgba(120, 205, 245, 0) 74%), radial-gradient(circle 4rem at 16% 24%, rgba(120, 205, 245, 0) 60%, rgba(120, 205, 245, 0.2) 67%, rgba(120, 205, 245, 0) 74%),
+4
View File
@@ -4,6 +4,10 @@
explicit [data-mantine-color-scheme] blocks. */ explicit [data-mantine-color-scheme] blocks. */
:root { :root {
/* bottom-nav indicator travel: spring (stiffness 320, damping 30) */
--flxn-nav-travel: linear(0, 0.0102, 0.0376, 0.0778, 0.1272, 0.1829, 0.2422, 0.3033, 0.3646, 0.4247, 0.4829, 0.5383, 0.5906, 0.6393, 0.6843, 0.7256, 0.7631, 0.7969, 0.8273, 0.8543, 0.8783, 0.8993, 0.9176, 0.9335, 0.9472, 0.9589, 0.9688, 0.9772, 0.9841, 0.9898, 0.9945, 0.9982, 1.0011, 1.0034, 1.0051, 1.0063, 1.0072, 1.0077, 1.0079, 1.0079, 1.0078);
--flxn-nav-travel-ms: 333ms;
/* accolades */ /* accolades */
--flxn-gold: gold; --flxn-gold: gold;
--flxn-silver: var(--mantine-color-gray-5); --flxn-silver: var(--mantine-color-gray-5);
+3 -4
View File
@@ -203,8 +203,7 @@
/* ---------- 4. Backdrop — studio key light + clay grain ---------- */ /* ---------- 4. Backdrop — studio key light + clay grain ---------- */
[data-theme="clay"] body::before { [data-theme="clay"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: -1; /* behind content — never tints text or surfaces */ z-index: -1; /* behind content — never tints text or surfaces */
@@ -212,7 +211,7 @@
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
} }
[data-theme="clay"][data-mantine-color-scheme="light"] body::before { [data-theme="clay"][data-mantine-color-scheme="light"] .flxn-theme-overlay {
background-image: radial-gradient( background-image: radial-gradient(
120% 70% at 50% 0%, 120% 70% at 50% 0%,
rgba(255, 252, 244, 0.65), rgba(255, 252, 244, 0.65),
@@ -221,7 +220,7 @@
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E"); url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
} }
[data-theme="clay"][data-mantine-color-scheme="dark"] body::before { [data-theme="clay"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
background-image: radial-gradient( background-image: radial-gradient(
110% 65% at 50% 0%, 110% 65% at 50% 0%,
rgba(255, 208, 158, 0.09), rgba(255, 208, 158, 0.09),
+1 -2
View File
@@ -363,8 +363,7 @@
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* 4. Sunburst fan — faint gold rays from top center */ /* 4. Sunburst fan — faint gold rays from top center */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
[data-theme="deco"] body::before { [data-theme="deco"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 9998; z-index: 9998;
+1 -2
View File
@@ -409,8 +409,7 @@
* 3. Full-page decoration: candlelit vignette (dark scheme only) * 3. Full-page decoration: candlelit vignette (dark scheme only)
* ---------------------------------------------------------------- */ * ---------------------------------------------------------------- */
[data-theme="gothic"][data-mantine-color-scheme="dark"] body::before { [data-theme="gothic"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 9998; z-index: 9998;
+58
View File
@@ -67,3 +67,61 @@
border: 1px solid var(--mantine-color-default-border); border: 1px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-md); border-radius: var(--mantine-radius-md);
} }
/* Bottom-nav active indicator: one element glides between tabs. The surface is
neutral on purpose — the accent belongs to the glyph, not a tint behind it. */
.flxn-nav-indicator {
position: absolute;
top: 0;
left: 0;
width: var(--nav-w, 0);
height: var(--nav-h, 0);
translate: var(--nav-x, 0) var(--nav-y, 0);
pointer-events: none;
opacity: 0;
z-index: 0;
transition: translate var(--flxn-nav-travel-ms) var(--flxn-nav-travel),
width var(--flxn-nav-travel-ms) var(--flxn-nav-travel),
height var(--flxn-nav-travel-ms) var(--flxn-nav-travel),
opacity 140ms ease-out;
}
.flxn-nav-indicator[data-placed="1"] {
opacity: 1;
}
.flxn-nav-indicator-shape {
display: block;
width: calc(100% + var(--flxn-nav-pad-x, 12px));
height: calc(100% + var(--flxn-nav-pad-y, 6px));
margin: calc(var(--flxn-nav-pad-y, 6px) / -2) calc(var(--flxn-nav-pad-x, 12px) / -2);
background: var(--flxn-nav-surface, var(--mantine-color-default));
border: 1px solid var(--flxn-nav-border, var(--mantine-color-default-border));
border-radius: var(--flxn-nav-radius, var(--mantine-radius-md));
box-shadow: var(--mantine-shadow-xs);
}
.flxn-nav-link {
/* lifted above the indicator, or an opaque fill covers the glyph */
position: relative;
z-index: 1;
}
.flxn-nav-link svg,
.flxn-nav-link p {
transition: color 140ms ease-out;
}
.flxn-nav-link[data-active] {
color: var(--flxn-nav-accent, var(--mantine-primary-color-filled));
}
@media (prefers-reduced-motion: no-preference) {
.flxn-nav-link[data-active] svg {
animation: flxn-nav-pop 320ms cubic-bezier(0.32, 0.72, 0, 1);
}
}
@keyframes flxn-nav-pop {
0% { scale: 1; }
38% { scale: 1.16; }
100% { scale: 1; }
}
@media (prefers-reduced-motion: reduce) {
.flxn-nav-indicator {
transition: opacity 120ms ease-out;
}
}
+3 -4
View File
@@ -153,8 +153,7 @@
/* 3. Fixed backdrop (below all content) */ /* 3. Fixed backdrop (below all content) */
/* ---------------------------------------------------------------- */ /* ---------------------------------------------------------------- */
[data-theme="ocean"] body::before { [data-theme="ocean"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: -1; z-index: -1;
@@ -163,7 +162,7 @@
/* Abyssal descent: deep teal fading to ink navy, sparse static bubbles /* Abyssal descent: deep teal fading to ink navy, sparse static bubbles
rising (bigger and brighter near the sea floor, fading toward the top). */ rising (bigger and brighter near the sea floor, fading toward the top). */
[data-theme="ocean"][data-mantine-color-scheme="dark"] body::before { [data-theme="ocean"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
background-color: #062632; background-color: #062632;
background-image: background-image:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='640' viewBox='0 0 260 640'%3E%3Cg fill='none' stroke='%239beee2'%3E%3Ccircle cx='36' cy='584' r='5' stroke-opacity='.13'/%3E%3Ccircle cx='44' cy='512' r='3' stroke-opacity='.1'/%3E%3Ccircle cx='210' cy='612' r='4' stroke-opacity='.12'/%3E%3Ccircle cx='202' cy='540' r='2.5' stroke-opacity='.09'/%3E%3Ccircle cx='128' cy='430' r='3.5' stroke-opacity='.1'/%3E%3Ccircle cx='120' cy='360' r='2' stroke-opacity='.08'/%3E%3Ccircle cx='232' cy='300' r='3' stroke-opacity='.09'/%3E%3Ccircle cx='60' cy='250' r='2.5' stroke-opacity='.08'/%3E%3Ccircle cx='150' cy='160' r='2' stroke-opacity='.07'/%3E%3Ccircle cx='90' cy='80' r='1.5' stroke-opacity='.06'/%3E%3C/g%3E%3Cg fill='%239beee2'%3E%3Ccircle cx='70' cy='470' r='1.2' fill-opacity='.1'/%3E%3Ccircle cx='180' cy='390' r='1' fill-opacity='.09'/%3E%3Ccircle cx='40' cy='330' r='1' fill-opacity='.08'/%3E%3Ccircle cx='222' cy='190' r='1' fill-opacity='.07'/%3E%3Ccircle cx='140' cy='40' r='1' fill-opacity='.05'/%3E%3C/g%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='640' viewBox='0 0 260 640'%3E%3Cg fill='none' stroke='%239beee2'%3E%3Ccircle cx='36' cy='584' r='5' stroke-opacity='.13'/%3E%3Ccircle cx='44' cy='512' r='3' stroke-opacity='.1'/%3E%3Ccircle cx='210' cy='612' r='4' stroke-opacity='.12'/%3E%3Ccircle cx='202' cy='540' r='2.5' stroke-opacity='.09'/%3E%3Ccircle cx='128' cy='430' r='3.5' stroke-opacity='.1'/%3E%3Ccircle cx='120' cy='360' r='2' stroke-opacity='.08'/%3E%3Ccircle cx='232' cy='300' r='3' stroke-opacity='.09'/%3E%3Ccircle cx='60' cy='250' r='2.5' stroke-opacity='.08'/%3E%3Ccircle cx='150' cy='160' r='2' stroke-opacity='.07'/%3E%3Ccircle cx='90' cy='80' r='1.5' stroke-opacity='.06'/%3E%3C/g%3E%3Cg fill='%239beee2'%3E%3Ccircle cx='70' cy='470' r='1.2' fill-opacity='.1'/%3E%3Ccircle cx='180' cy='390' r='1' fill-opacity='.09'/%3E%3Ccircle cx='40' cy='330' r='1' fill-opacity='.08'/%3E%3Ccircle cx='222' cy='190' r='1' fill-opacity='.07'/%3E%3Ccircle cx='140' cy='40' r='1' fill-opacity='.05'/%3E%3C/g%3E%3C/svg%3E"),
@@ -175,7 +174,7 @@
/* Sunlit shallows: seafoam fading to a sandy floor, with a faint white /* Sunlit shallows: seafoam fading to a sandy floor, with a faint white
caustic ripple tile — refracted light, kept very subtle. */ caustic ripple tile — refracted light, kept very subtle. */
[data-theme="ocean"][data-mantine-color-scheme="light"] body::before { [data-theme="ocean"][data-mantine-color-scheme="light"] .flxn-theme-overlay {
background-color: #eaf6f6; background-color: #eaf6f6;
background-image: background-image:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='220' viewBox='0 0 320 220'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1.4'%3E%3Cpath d='M0 40 Q 40 10 80 42 T 160 44 T 240 38 T 320 42' stroke-opacity='.4'/%3E%3Cpath d='M0 110 Q 45 82 90 112 T 180 110 T 268 114 T 320 108' stroke-opacity='.3'/%3E%3Cpath d='M0 180 Q 50 152 100 182 T 200 178 T 300 184 T 320 180' stroke-opacity='.35'/%3E%3C/g%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='220' viewBox='0 0 320 220'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1.4'%3E%3Cpath d='M0 40 Q 40 10 80 42 T 160 44 T 240 38 T 320 42' stroke-opacity='.4'/%3E%3Cpath d='M0 110 Q 45 82 90 112 T 180 110 T 268 114 T 320 108' stroke-opacity='.3'/%3E%3Cpath d='M0 180 Q 50 152 100 182 T 200 178 T 300 184 T 320 180' stroke-opacity='.35'/%3E%3C/g%3E%3C/svg%3E"),
+2 -4
View File
@@ -414,8 +414,7 @@
* 3. Full-page decoration: worn parchment edges / torchlight * 3. Full-page decoration: worn parchment edges / torchlight
* ---------------------------------------------------------------- */ * ---------------------------------------------------------------- */
[data-theme="quest"][data-mantine-color-scheme="light"] body::before { [data-theme="quest"][data-mantine-color-scheme="light"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 9998; z-index: 9998;
@@ -426,8 +425,7 @@
radial-gradient(55% 38% at 95% 98%, rgba(122, 91, 42, 0.07), transparent 70%); radial-gradient(55% 38% at 95% 98%, rgba(122, 91, 42, 0.07), transparent 70%);
} }
[data-theme="quest"][data-mantine-color-scheme="dark"] body::before { [data-theme="quest"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 9998; z-index: 9998;
+2 -3
View File
@@ -151,8 +151,7 @@
/* 3. Paper grain / chalk dust — one fixed overlay, feTurbulence */ /* 3. Paper grain / chalk dust — one fixed overlay, feTurbulence */
/* ---------------------------------------------------------------- */ /* ---------------------------------------------------------------- */
[data-theme="sketch"] body::before { [data-theme="sketch"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 9998; z-index: 9998;
@@ -163,7 +162,7 @@
opacity: 0.05; opacity: 0.05;
} }
[data-theme="sketch"][data-mantine-color-scheme="dark"] body::before { [data-theme="sketch"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
mix-blend-mode: screen; mix-blend-mode: screen;
opacity: 0.07; opacity: 0.07;
} }
+2 -3
View File
@@ -575,8 +575,7 @@
} }
/* 4. Decoration: fixed scanline overlay + faint RGB aperture mask */ /* 4. Decoration: fixed scanline overlay + faint RGB aperture mask */
[data-theme="terminal"] body::before { [data-theme="terminal"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
inset: 0; inset: 0;
z-index: 9998; z-index: 9998;
@@ -589,7 +588,7 @@
} }
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
[data-theme="terminal"] body::before { [data-theme="terminal"] .flxn-theme-overlay {
animation: terminal-phosphor-drift 9s ease-in-out infinite; animation: terminal-phosphor-drift 9s ease-in-out infinite;
} }
@keyframes terminal-phosphor-drift { @keyframes terminal-phosphor-drift {
+1 -2
View File
@@ -150,8 +150,7 @@
/* ---------- 3. Decoration: static perspective floor-grid hint ---------- */ /* ---------- 3. Decoration: static perspective floor-grid hint ---------- */
[data-theme="wireframe"] body::before { [data-theme="wireframe"] .flxn-theme-overlay {
content: "";
position: fixed; position: fixed;
left: -30vw; left: -30vw;
right: -30vw; right: -30vw;
+46 -32
View File
@@ -5,7 +5,7 @@ import {
} from "@tanstack/react-start"; } from "@tanstack/react-start";
import { getRequest } from "@tanstack/react-start/server"; import { getRequest } from "@tanstack/react-start/server";
import { Logger } from "@/lib/logger"; import { Logger } from "@/lib/logger";
import { isAdminRole } from "@/features/core/utils/roles"; import { isAdminRole, isDeveloperRole } from "@/features/core/utils/roles";
import z from "zod"; import z from "zod";
const logger = new Logger("Middleware"); const logger = new Logger("Middleware");
@@ -70,41 +70,55 @@ export const superTokensFunctionMiddleware = createMiddleware({
} }
}); });
export const superTokensAdminFunctionMiddleware = createMiddleware({ const createRoleFunctionMiddleware = (
type: "function", hasRole: (roles?: string[] | null) => boolean,
}).server(async ({ next, serverFnMeta }) => { label: string
const request = getRequest(); ) =>
createMiddleware({
type: "function",
}).server(async ({ next, serverFnMeta }) => {
const request = getRequest();
try { try {
const context = await getSessionContext(request, { isServerFunction: true }); const context = await getSessionContext(request, { isServerFunction: true });
if (isAdminRole(context.roles)) { if (hasRole(context.roles)) {
return next({ context }); return next({ context });
}
logger.error(`Unauthorized user in ${label} function.`, {
userAuthId: context.userAuthId,
roles: context.roles,
});
const { recordDeniedServerFn } = await import("./activities");
recordDeniedServerFn(request, context.metadata, serverFnMeta);
throw new Error("Unauthorized");
} catch (error: any) {
if (error.message === "SESSION_REFRESH_REQUIRED") {
throw new Response(
JSON.stringify({
error: "SESSION_REFRESH_REQUIRED",
message: "Session needs to be refreshed"
}),
{
status: 401,
headers: { "Content-Type": "application/json" }
}
);
}
throw error;
} }
});
logger.error("Unauthorized user in admin function.", { export const superTokensAdminFunctionMiddleware = createRoleFunctionMiddleware(
userAuthId: context.userAuthId, isAdminRole,
roles: context.roles, "admin"
}); );
const { recordDeniedServerFn } = await import("./activities");
recordDeniedServerFn(request, context.metadata, serverFnMeta); export const superTokensDeveloperFunctionMiddleware = createRoleFunctionMiddleware(
throw new Error("Unauthorized"); isDeveloperRole,
} catch (error: any) { "developer"
if (error.message === "SESSION_REFRESH_REQUIRED") { );
throw new Response(
JSON.stringify({
error: "SESSION_REFRESH_REQUIRED",
message: "Session needs to be refreshed"
}),
{
status: 401,
headers: { "Content-Type": "application/json" }
}
);
}
throw error;
}
});
export const setUserMetadata = createServerFn({ method: "POST" }) export const setUserMetadata = createServerFn({ method: "POST" })
.validator( .validator(