Compare commits
6
Commits
e55a167df2
...
0493c08dae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0493c08dae | ||
|
|
9febb8bb60 | ||
|
|
a754fc0282 | ||
|
|
6f17a5d4a8 | ||
|
|
9ed3f7169e | ||
|
|
c4a9d47180 |
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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] {
|
||||||
@@ -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;
|
||||||
@@ -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>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -2091,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 "
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -2091,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 "
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -2091,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 "
|
||||||
|
|
||||||
|
|||||||
@@ -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 "地区予選の試合は順不同です"
|
||||||
|
|
||||||
@@ -2091,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 "進行状況 "
|
||||||
|
|
||||||
|
|||||||
@@ -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,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);
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user