diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4c091e87..c09f936a 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -30,6 +30,9 @@
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "biomejs.biome"
+ },
"[mdx]": {
"editor.defaultFormatter": "unifiedjs.vscode-mdx"
},
diff --git a/index.html b/index.html
index 156dc2de..2dd4b98f 100644
--- a/index.html
+++ b/index.html
@@ -1,5 +1,5 @@
-
+
diff --git a/package.json b/package.json
index d69b5066..a9b52794 100644
--- a/package.json
+++ b/package.json
@@ -14,14 +14,15 @@
"test": "vitest"
},
"dependencies": {
- "@ark-ui/react": "^5.23.0",
- "@fontsource-variable/inconsolata": "^5.2.7",
- "@fontsource-variable/oswald": "^5.2.6",
- "@fontsource-variable/raleway": "^5.2.6",
- "@react-spring/three": "^10.0.2",
- "@react-spring/web": "^10.0.2",
- "@react-three/drei": "^10.7.5",
- "@react-three/fiber": "^9.3.0",
+ "@ark-ui/react": "^5.31.0",
+ "@fontsource-variable/inconsolata": "^5.2.8",
+ "@fontsource-variable/oswald": "^5.2.8",
+ "@fontsource-variable/raleway": "^5.2.8",
+ "@mdx-js/mdx": "^3.1.1",
+ "@react-spring/three": "^10.0.3",
+ "@react-spring/web": "^10.0.3",
+ "@react-three/drei": "^10.7.7",
+ "@react-three/fiber": "^9.5.0",
"@react-three/postprocessing": "^3.0.4",
"@reduxjs/toolkit": "^2.9.0",
"@standard-schema/spec": "^1.0.0",
@@ -30,52 +31,57 @@
"@std/path": "jsr:^1.1.2",
"@std/random": "jsr:^0.1.2",
"@std/text": "jsr:^1.0.16",
- "@tanstack/react-form": "^1.19.5",
- "@tanstack/react-pacer": "^0.16.2",
- "@tanstack/react-query": "^5.87.4",
- "@tanstack/react-router": "^1.131.36",
- "@tanstack/react-router-devtools": "^1.131.36",
+ "@tanstack/react-form": "^1.28.3",
+ "@tanstack/react-pacer": "^0.20.0",
+ "@tanstack/react-query": "^5.90.21",
+ "@tanstack/react-router": "^1.161.1",
"@tanstack/react-table": "^8.21.3",
- "@zag-js/color-utils": "^1.22.1",
- "@zag-js/file-utils": "^1.22.1",
+ "@zag-js/color-utils": "^1.33.1",
+ "@zag-js/file-utils": "^1.33.1",
"bsmap": "^2.2.9",
"date-fns": "^4.1.0",
"fflate": "^0.8.2",
"file-saver": "^2.0.5",
"idb": "^8.0.3",
- "lucide-react": "^0.543.0",
- "react": "^19.1.1",
- "react-dom": "^19.1.1",
+ "lucide-react": "^0.574.0",
+ "postprocessing": "^6.38.2",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
"react-redux": "^9.2.0",
"redux-state-sync": "^3.1.4",
"redux-undo": "^1.1.0",
- "three": "^0.174.0",
- "three-stdlib": "^2.36.0",
+ "three": "^0.183.0",
"unstorage": "^1.17.1",
"valibot": "^1.1.0",
"waveform-data": "^4.5.2"
},
"devDependencies": {
"@biomejs/biome": "2.2.4",
- "@pandacss/dev": "^1.3.0",
- "@pandacss/preset-base": "^1.3.0",
- "@tanstack/router-cli": "^1.131.36",
- "@tanstack/router-plugin": "^1.131.36",
- "@tanstack/virtual-file-routes": "^1.131.2",
+ "@pandacss/dev": "^1.8.2",
+ "@pandacss/types": "^1.8.2",
+ "@tanstack/devtools-vite": "^0.5.1",
+ "@tanstack/react-devtools": "^0.9.6",
+ "@tanstack/react-form-devtools": "^0.2.16",
+ "@tanstack/react-pacer-devtools": "^0.5.2",
+ "@tanstack/react-query-devtools": "^5.91.3",
+ "@tanstack/react-router-devtools": "^1.161.1",
+ "@tanstack/router-cli": "^1.161.1",
+ "@tanstack/router-plugin": "^1.161.1",
+ "@tanstack/virtual-file-routes": "^1.154.7",
"@types/file-saver": "^2.0.7",
"@types/node": "^22.18.1",
- "@types/react": "^19.1.12",
- "@types/react-dom": "^19.1.9",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
"@types/redux-state-sync": "^3.1.10",
- "@types/three": "^0.174.0",
+ "@types/three": "^0.183.0",
"@velite/plugin-vite": "^0.0.1",
"@vite-pwa/assets-generator": "^1.0.1",
- "@vitejs/plugin-react": "^5.0.2",
+ "@vitejs/plugin-react": "^5.1.4",
"concurrently": "^9.2.1",
"lefthook": "^1.12.4",
"mdx": "^0.3.1",
- "rehype-expressive-code": "^0.41.3",
- "rehype-github-alerts": "^4.1.1",
+ "rehype-expressive-code": "^0.41.6",
+ "rehype-github-alerts": "^4.2.0",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.1",
"typescript": "^5.9.2",
@@ -85,7 +91,6 @@
"vitest": "^3.2.4"
},
"resolutions": {
- "@types/react": "^18",
- "redux": "^5"
+ "three-stdlib": "^2.36.1"
}
}
diff --git a/panda.config.ts b/panda.config.ts
index a3130e3a..152dd28b 100644
--- a/panda.config.ts
+++ b/panda.config.ts
@@ -1,14 +1,24 @@
-import { defineConfig } from "@pandacss/dev";
-import { default as base } from "@pandacss/preset-base";
+import { defineConfig, definePlugin } from "@pandacss/dev";
import { default as beatmapper } from "./src/styles/preset";
+const removePandaTokens = definePlugin({
+ name: "remove-colors",
+ hooks: {
+ "preset:resolved": ({ utils, preset, name }) => {
+ if (name === "@pandacss/preset-panda") return utils.omit(preset, ["theme.tokens.colors", "theme.semanticTokens.colors"]);
+ return preset;
+ },
+ },
+});
+
export default defineConfig({
preflight: true,
- include: ["./src/**/*.{js,jsx,ts,tsx}", "./pages/**/*.{js,jsx,ts,tsx}"],
+ include: ["./src/**/*.{js,jsx,ts,tsx}"],
importMap: "$:styled-system",
outdir: "styled-system",
- presets: [base, beatmapper({})],
+ presets: ["@pandacss/preset-base", "@pandacss/preset-panda", beatmapper({})],
+ plugins: [removePandaTokens],
jsxFramework: "react",
jsxStyleProps: "none",
shorthands: false,
diff --git a/src/components/app/atoms/file.tsx b/src/components/app/atoms/file.tsx
deleted file mode 100644
index 223baedc..00000000
--- a/src/components/app/atoms/file.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { ReactNode } from "react";
-
-import { useLocalFileQuery } from "$/components/app/hooks";
-import { convertFileToDataUrl } from "$/helpers/file.helpers";
-
-export interface LocalFileProps {
- filename: string;
- fallback?: ReactNode;
- children: (src: string | undefined, isLoading: boolean) => ReactNode;
-}
-export function LocalFilePreview({ filename, fallback, children }: LocalFileProps) {
- const { data: url, isFetching } = useLocalFileQuery(filename, {
- queryKeySuffix: "preview",
- transform: async (file) => await convertFileToDataUrl(file),
- });
- if (!url) return fallback;
- return children(url, isFetching);
-}
diff --git a/src/components/app/atoms/index.ts b/src/components/app/atoms/index.ts
index db45f6fb..51ce3519 100644
--- a/src/components/app/atoms/index.ts
+++ b/src/components/app/atoms/index.ts
@@ -1 +1 @@
-export { LocalFilePreview, type LocalFileProps } from "./file";
+export { LocalFile } from "./local-file";
diff --git a/src/components/app/atoms/local-file.tsx b/src/components/app/atoms/local-file.tsx
new file mode 100644
index 00000000..1e8b8b11
--- /dev/null
+++ b/src/components/app/atoms/local-file.tsx
@@ -0,0 +1,20 @@
+import type { ReactNode } from "react";
+
+import { useLocalFileQuery } from "$/components/app/hooks/local-file.hooks";
+import { convertFileToDataUrl } from "$/helpers/file.helpers";
+
+export interface LocalFileProps {
+ filename: string;
+ fallback?: ReactNode;
+ children: (src: string | undefined, isLoading: boolean) => ReactNode;
+}
+export function LocalFile({ filename, fallback, children }: LocalFileProps) {
+ const { data: url, isLoading } = useLocalFileQuery(filename, {
+ queryKey: ["supplier"],
+ transformFile: async (file) => await convertFileToDataUrl(file),
+ });
+
+ if (!url) return fallback;
+
+ return children(url, isLoading);
+}
diff --git a/src/components/app/compositions/file-upload.tsx b/src/components/app/compositions/file-upload.tsx
deleted file mode 100644
index 9866ca40..00000000
--- a/src/components/app/compositions/file-upload.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import type { FileUploadFileAcceptDetails } from "@ark-ui/react/file-upload";
-import type { ComponentProps } from "react";
-
-import { APP_TOASTER, MAP_ARCHIVE_FILE_ACCEPT_TYPE } from "$/components/app/constants";
-import { useLocalFileQuery } from "$/components/app/hooks";
-import { FileUpload } from "$/components/ui/compositions";
-import { addSongFromFile } from "$/store/actions";
-import { useAppDispatch, useAppSelector } from "$/store/hooks";
-import { selectSongIds } from "$/store/selectors";
-
-export function LocalFileUpload({ filename, children, ...rest }: ComponentProps & { filename: string }) {
- const { data: currentFiles, isSuccess } = useLocalFileQuery(filename, {
- queryKeySuffix: "picker",
- transform: (file) => (file ? [file] : []),
- });
-
- return (
-
- {children}
-
- );
-}
-
-export function MapArchiveFileUpload({ onFileAccept, ...rest }: ComponentProps) {
- const dispatch = useAppDispatch();
- const songIds = useAppSelector(selectSongIds);
-
- const handleFileAccept = async (details: FileUploadFileAcceptDetails) => {
- for (const file of details.files) {
- try {
- await dispatch(addSongFromFile({ file, options: { currentSongIds: songIds } }));
- } catch (err) {
- console.error("Could not import map:", err);
- if (onFileAccept) onFileAccept({ files: [] });
- return APP_TOASTER.create({
- id: "import-map-fail",
- type: "error",
- description: "Could not import map. See console for more info.",
- });
- }
- }
- if (onFileAccept) onFileAccept(details);
- };
-
- return (
-
- Map Archive File
-
- );
-}
diff --git a/src/components/app/compositions/file.tsx b/src/components/app/compositions/file.tsx
deleted file mode 100644
index 80761fff..00000000
--- a/src/components/app/compositions/file.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { Assign } from "@ark-ui/react";
-import { type ComponentProps, type CSSProperties, type PropsWithoutRef, useMemo } from "react";
-
-import { LocalFilePreview, type LocalFileProps } from "$/components/app/atoms";
-import { Spinner } from "$/components/ui/compositions";
-import { BeatmapFilestore } from "$/services/file.service";
-import type { SongId } from "$/types";
-import { styled } from "$:styled-system/jsx";
-import { center } from "$:styled-system/patterns";
-
-interface CoverArtProps extends PropsWithoutRef> {
- sid: SongId;
- width?: CSSProperties["width"];
-}
-export function CoverArtFilePreview({ sid, width, ...rest }: Assign, CoverArtProps>) {
- const style = useMemo(() => ({ width, height: width }), [width]);
- return (
-
- }>
- {(src) => }
-
-
- );
-}
-
-const CoverArtWrapper = styled("div", {
- base: center.raw(),
-});
-const CoverArtImage = styled("img", {
- base: {
- objectFit: "cover",
- borderRadius: "sm",
- aspectRatio: "square",
- },
-});
diff --git a/src/components/app/compositions/index.ts b/src/components/app/compositions/index.ts
index fb2599b7..9295b3d9 100644
--- a/src/components/app/compositions/index.ts
+++ b/src/components/app/compositions/index.ts
@@ -1,5 +1,2 @@
-export { CoverArtFilePreview } from "./file";
-export { LocalFileUpload, MapArchiveFileUpload } from "./file-upload";
+export { CoverArtFile } from "./local-file";
export { default as Logo } from "./logo";
-export { AppPrompter, useAppPrompterContext } from "./prompter";
-export { Shortcut } from "./shortcut";
diff --git a/src/components/app/compositions/local-file.tsx b/src/components/app/compositions/local-file.tsx
new file mode 100644
index 00000000..8eaa7cee
--- /dev/null
+++ b/src/components/app/compositions/local-file.tsx
@@ -0,0 +1,29 @@
+import type { Assign } from "@ark-ui/react";
+import { type ComponentProps, useMemo } from "react";
+
+import { LocalFile } from "$/components/app/atoms";
+import { Spinner } from "$/components/ui/compositions";
+import { Center, styled } from "$:styled-system/jsx";
+
+export interface CoverArtProps {
+ boxSize?: number;
+}
+export function CoverArtFile({ filename, boxSize, ...rest }: Assign, CoverArtProps & { filename: string }>) {
+ const style = useMemo(() => ({ width: boxSize, height: boxSize }), [boxSize]);
+
+ return (
+
+ }>
+ {(src) => }
+
+
+ );
+}
+
+const CoverArt = styled("img", {
+ base: {
+ objectFit: "cover",
+ borderRadius: "sm",
+ aspectRatio: "square",
+ },
+});
diff --git a/src/components/app/compositions/logo.tsx b/src/components/app/compositions/logo.tsx
index 797a567c..5bdd93f7 100644
--- a/src/components/app/compositions/logo.tsx
+++ b/src/components/app/compositions/logo.tsx
@@ -1,9 +1,9 @@
import { animated as a, useSpring } from "@react-spring/three";
import { Canvas } from "@react-three/fiber";
-import { Link } from "@tanstack/react-router";
+import { Link, useRouteContext } from "@tanstack/react-router";
import { createColorNote, NoteDirection } from "bsmap";
import type { EnvironmentAllName } from "bsmap/types";
-import { type DateArg, endOfMonth, endOfWeek, isWithinInterval, startOfMonth, startOfWeek } from "date-fns";
+import { getYear, isThisMonth, isThisWeek, setYear } from "date-fns";
import { useMemo, useRef, useState } from "react";
import { ColorNote } from "$/components/scene/compositions";
@@ -12,26 +12,29 @@ import { HStack, Stack, styled } from "$:styled-system/jsx";
const MOCK_NOTE = createColorNote({ direction: NoteDirection.DOWN });
-function checkDate(date: `${number}/${number}`, start: (date: DateArg) => Date, end: (date: DateArg) => Date) {
- const today = new Date();
- return isWithinInterval(today, { start: start(`${date}/${today.getFullYear()}`), end: end(`${date}/${today.getFullYear()}`) });
-}
-
-function deriveNoteColor() {
+function deriveNoteColor(now: number) {
let environment: EnvironmentAllName = "DefaultEnvironment" as const;
- if (checkDate("06/01", startOfMonth, endOfMonth)) environment = "GagaEnvironment";
- if (checkDate("10/31", startOfWeek, endOfWeek)) environment = "HalloweenEnvironment";
+
+ if (isThisMonth(setYear("06/01", getYear(now)))) {
+ environment = "GagaEnvironment";
+ }
+ if (isThisWeek(setYear("10/31", getYear(now)))) {
+ environment = "HalloweenEnvironment";
+ }
+
const { colorLeft, colorRight } = deriveColorSchemeFromEnvironment(environment);
- if (import.meta.env.DEV) return colorRight;
- return colorLeft;
+ return import.meta.env.DEV ? colorRight : colorLeft;
}
interface Props {
size?: "full" | "mini";
}
function Logo({ size = "full" }: Props) {
+ const { now } = useRouteContext({ from: "__root__" });
+
const [isHovering, setIsHovering] = useState(false);
- const color = useRef(deriveNoteColor());
+
+ const color = useRef(deriveNoteColor(now));
const [spring] = useSpring(() => ({ rotation: isHovering ? 0 : -0.35 }), [isHovering]);
@@ -47,7 +50,7 @@ function Logo({ size = "full" }: Props) {