diff --git a/client/package-lock.json b/client/package-lock.json index f58226f..16e15af 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -15,14 +15,22 @@ "lucide-react": "^0.503.0", "react": "^19.0.0", "react-dom": "^19.0.0", + + "react-howler": "^5.2.0" + "react-router-dom": "^7.5.2" + }, "devDependencies": { "@eslint/js": "^9.21.0", "@tailwindcss/postcss": "^4.1.4", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", + + "@types/react-howler": "^5.2.3", + "@types/react-router-dom": "^5.3.3", + "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.21", "eslint": "^9.21.0", @@ -1290,6 +1298,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.6.tgz", "integrity": "sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==", + "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.0", @@ -1409,57 +1418,646 @@ } } }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA= + + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", + "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz", + "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz", + "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz", + "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz", + "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz", + "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz", + "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz", + "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz", + "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz", + "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz", + "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz", + "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz", + "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz", + "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz", + "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz", + "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz", + "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz", + "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz", + "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz", + "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz", + "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.4.tgz", + "integrity": "sha512-MT5118zaiO6x6hNA04OWInuAiP1YISXql8Z+/Y8iisV5nuhM8VXlyhRuqc2PEviPszcXI66W44bCIk500Oolhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.29.2", + "tailwindcss": "4.1.4" } }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "node_modules/@tailwindcss/oxide": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.4.tgz", + "integrity": "sha512-p5wOpXyOJx7mKh5MXh5oKk+kqcz8T+bA3z/5VWWeQwFrmuBItGwz8Y2CHk/sJ+dNb9B0nYFfn0rj/cKHZyjahQ==", + "dev": true, "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "engines": { + "node": ">= 10" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.4", + "@tailwindcss/oxide-darwin-arm64": "4.1.4", + "@tailwindcss/oxide-darwin-x64": "4.1.4", + "@tailwindcss/oxide-freebsd-x64": "4.1.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.4", + "@tailwindcss/oxide-linux-x64-musl": "4.1.4", + "@tailwindcss/oxide-wasm32-wasi": "4.1.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.4" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.4.tgz", + "integrity": "sha512-xMMAe/SaCN/vHfQYui3fqaBDEXMu22BVwQ33veLc8ep+DNy7CWN52L+TTG9y1K397w9nkzv+Mw+mZWISiqhmlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.4.tgz", + "integrity": "sha512-JGRj0SYFuDuAGilWFBlshcexev2hOKfNkoX+0QTksKYq2zgF9VY/vVMq9m8IObYnLna0Xlg+ytCi2FN2rOL0Sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.4.tgz", + "integrity": "sha512-sdDeLNvs3cYeWsEJ4H1DvjOzaGios4QbBTNLVLVs0XQ0V95bffT3+scptzYGPMjm7xv4+qMhCDrkHwhnUySEzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.4.tgz", + "integrity": "sha512-VHxAqxqdghM83HslPhRsNhHo91McsxRJaEnShJOMu8mHmEj9Ig7ToHJtDukkuLWLzLboh2XSjq/0zO6wgvykNA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.4.tgz", + "integrity": "sha512-OTU/m/eV4gQKxy9r5acuesqaymyeSCnsx1cFto/I1WhPmi5HDxX1nkzb8KYBiwkHIGg7CTfo/AcGzoXAJBxLfg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.4.tgz", + "integrity": "sha512-hKlLNvbmUC6z5g/J4H+Zx7f7w15whSVImokLPmP6ff1QqTVE+TxUM9PGuNsjHvkvlHUtGTdDnOvGNSEUiXI1Ww==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.4.tgz", + "integrity": "sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==", + "cpu": [ + "arm64" + ], + "dev": true, + + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, + + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.4.tgz", + "integrity": "sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "node_modules/@radix-ui/react-use-layout-effect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.40.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", @@ -1913,6 +2511,7 @@ "node": ">= 10" } }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.4.tgz", @@ -2060,10 +2659,17 @@ "dev": true, "license": "MIT" }, + + "node_modules/@types/howler": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.12.tgz", + "integrity": "sha512-hy769UICzOSdK0Kn1FBk4gN+lswcj1EKRkmiDtMkUGvFfYJzgaDXmVXkSShS2m89ERAatGIPnTUlp2HhfkVo5g==", + "node_modules/@types/history": { "version": "4.7.11", "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true, "license": "MIT" }, @@ -2094,6 +2700,23 @@ "@types/react": "^19.0.0" } }, + + "node_modules/@types/react-howler": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/react-howler/-/react-howler-5.2.3.tgz", + "integrity": "sha512-0XJ+vHLgobVcc8G8f3jlnfIv3FVnUvEuxOyq3TwW9qNBeko7EhHDc9QYtY7mUTcrT86mPTAVCKWitY+TyzHWZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/howler": "*", + "@types/react": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz", + "integrity": "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==", + "node_modules/@types/react-router": { "version": "5.1.20", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", @@ -2121,14 +2744,15 @@ "version": "8.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", "integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/type-utils": "8.31.0", - "@typescript-eslint/utils": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/scope-manager": "8.31.1", + "@typescript-eslint/type-utils": "8.31.1", + "@typescript-eslint/utils": "8.31.1", + "@typescript-eslint/visitor-keys": "8.31.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2148,16 +2772,22 @@ } }, "node_modules/@typescript-eslint/parser": { + + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz", + "integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==", + "version": "8.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", "integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/scope-manager": "8.31.1", + "@typescript-eslint/types": "8.31.1", + "@typescript-eslint/typescript-estree": "8.31.1", + "@typescript-eslint/visitor-keys": "8.31.1", "debug": "^4.3.4" }, "engines": { @@ -2173,14 +2803,20 @@ } }, "node_modules/@typescript-eslint/scope-manager": { + + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz", + "integrity": "sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==", + "version": "8.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", "integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0" + "@typescript-eslint/types": "8.31.1", + "@typescript-eslint/visitor-keys": "8.31.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2191,14 +2827,20 @@ } }, "node_modules/@typescript-eslint/type-utils": { + + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz", + "integrity": "sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==", + "version": "8.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", "integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/utils": "8.31.0", + "@typescript-eslint/typescript-estree": "8.31.1", + "@typescript-eslint/utils": "8.31.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -2215,9 +2857,15 @@ } }, "node_modules/@typescript-eslint/types": { + + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.1.tgz", + "integrity": "sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==", + "version": "8.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", "integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", + "dev": true, "license": "MIT", "engines": { @@ -2229,14 +2877,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { + + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz", + "integrity": "sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==", + "version": "8.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", "integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/types": "8.31.1", + "@typescript-eslint/visitor-keys": "8.31.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2295,16 +2949,22 @@ } }, "node_modules/@typescript-eslint/utils": { + + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.1.tgz", + "integrity": "sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==", + "version": "8.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", "integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0" + "@typescript-eslint/scope-manager": "8.31.1", + "@typescript-eslint/types": "8.31.1", + "@typescript-eslint/typescript-estree": "8.31.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2319,13 +2979,19 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { + + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz", + "integrity": "sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==", + "version": "8.31.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", "integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.31.0", + "@typescript-eslint/types": "8.31.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -3254,6 +3920,12 @@ "react-is": "^16.7.0" } }, + "node_modules/howler": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz", + "integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4053,6 +4725,16 @@ "react": "^19.1.0" } }, + "node_modules/react-howler": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-howler/-/react-howler-5.2.0.tgz", + "integrity": "sha512-oDK+zML0MHf3nVNM4lMxh+re87NDa7fHowea2WK8197yqnMiZfPVHoMXtfb/PtuoOsWLO06vmEAtovwTRWpTFg==", + "license": "MIT", + "dependencies": { + "howler": "^2.2.0", + "prop-types": "^15.5.6" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -4217,9 +4899,15 @@ } }, "node_modules/rollup": { + + "version": "4.40.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", + "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==", + "version": "4.40.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, "license": "MIT", "dependencies": { @@ -4233,26 +4921,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.0", - "@rollup/rollup-android-arm64": "4.40.0", - "@rollup/rollup-darwin-arm64": "4.40.0", - "@rollup/rollup-darwin-x64": "4.40.0", - "@rollup/rollup-freebsd-arm64": "4.40.0", - "@rollup/rollup-freebsd-x64": "4.40.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", - "@rollup/rollup-linux-arm-musleabihf": "4.40.0", - "@rollup/rollup-linux-arm64-gnu": "4.40.0", - "@rollup/rollup-linux-arm64-musl": "4.40.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-musl": "4.40.0", - "@rollup/rollup-linux-s390x-gnu": "4.40.0", - "@rollup/rollup-linux-x64-gnu": "4.40.0", - "@rollup/rollup-linux-x64-musl": "4.40.0", - "@rollup/rollup-win32-arm64-msvc": "4.40.0", - "@rollup/rollup-win32-ia32-msvc": "4.40.0", - "@rollup/rollup-win32-x64-msvc": "4.40.0", + "@rollup/rollup-android-arm-eabi": "4.40.1", + "@rollup/rollup-android-arm64": "4.40.1", + "@rollup/rollup-darwin-arm64": "4.40.1", + "@rollup/rollup-darwin-x64": "4.40.1", + "@rollup/rollup-freebsd-arm64": "4.40.1", + "@rollup/rollup-freebsd-x64": "4.40.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", + "@rollup/rollup-linux-arm-musleabihf": "4.40.1", + "@rollup/rollup-linux-arm64-gnu": "4.40.1", + "@rollup/rollup-linux-arm64-musl": "4.40.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", + "@rollup/rollup-linux-riscv64-gnu": "4.40.1", + "@rollup/rollup-linux-riscv64-musl": "4.40.1", + "@rollup/rollup-linux-s390x-gnu": "4.40.1", + "@rollup/rollup-linux-x64-gnu": "4.40.1", + "@rollup/rollup-linux-x64-musl": "4.40.1", + "@rollup/rollup-win32-arm64-msvc": "4.40.1", + "@rollup/rollup-win32-ia32-msvc": "4.40.1", + "@rollup/rollup-win32-x64-msvc": "4.40.1", "fsevents": "~2.3.2" } }, @@ -4510,15 +5198,21 @@ } }, "node_modules/typescript-eslint": { + + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.1.tgz", + "integrity": "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==", + "version": "8.31.0", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.0.tgz", "integrity": "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.31.0", - "@typescript-eslint/parser": "8.31.0", - "@typescript-eslint/utils": "8.31.0" + "@typescript-eslint/eslint-plugin": "8.31.1", + "@typescript-eslint/parser": "8.31.1", + "@typescript-eslint/utils": "8.31.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/client/package.json b/client/package.json index 49c6805..aa73012 100644 --- a/client/package.json +++ b/client/package.json @@ -19,14 +19,22 @@ "lucide-react": "^0.503.0", "react": "^19.0.0", "react-dom": "^19.0.0", + + "react-howler": "^5.2.0" + "react-router-dom": "^7.5.2" + }, "devDependencies": { "@eslint/js": "^9.21.0", "@tailwindcss/postcss": "^4.1.4", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", + + "@types/react-howler": "^5.2.3", + "@types/react-router-dom": "^5.3.3", + "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.21", "eslint": "^9.21.0", diff --git a/client/public/audio/Dan_correct/Dan-5inarow.wav b/client/public/audio/Dan_correct/Dan-5inarow.wav new file mode 100644 index 0000000..e827ed3 Binary files /dev/null and b/client/public/audio/Dan_correct/Dan-5inarow.wav differ diff --git a/client/public/audio/Dan_correct/Dan-correct-1.wav b/client/public/audio/Dan_correct/Dan-correct-1.wav new file mode 100644 index 0000000..d4f0b41 Binary files /dev/null and b/client/public/audio/Dan_correct/Dan-correct-1.wav differ diff --git a/client/public/audio/Dan_correct/Dan-correct-2.wav b/client/public/audio/Dan_correct/Dan-correct-2.wav new file mode 100644 index 0000000..b83119e Binary files /dev/null and b/client/public/audio/Dan_correct/Dan-correct-2.wav differ diff --git a/client/public/audio/Dan_correct/Dan-correct-3.wav b/client/public/audio/Dan_correct/Dan-correct-3.wav new file mode 100644 index 0000000..8870af3 Binary files /dev/null and b/client/public/audio/Dan_correct/Dan-correct-3.wav differ diff --git a/client/public/audio/Dan_correct/Dan-correctStar.wav b/client/public/audio/Dan_correct/Dan-correctStar.wav new file mode 100644 index 0000000..6acae3a Binary files /dev/null and b/client/public/audio/Dan_correct/Dan-correctStar.wav differ diff --git a/client/public/audio/Dan_incorrect/Dan-firstincorrect.wav b/client/public/audio/Dan_incorrect/Dan-firstincorrect.wav new file mode 100644 index 0000000..7f14ab8 Binary files /dev/null and b/client/public/audio/Dan_incorrect/Dan-firstincorrect.wav differ diff --git a/client/public/openai-fm-echo-mad-scientist.wav b/client/public/audio/Dan_incorrect/Dan-incorrect-1.wav similarity index 100% rename from client/public/openai-fm-echo-mad-scientist.wav rename to client/public/audio/Dan_incorrect/Dan-incorrect-1.wav diff --git a/client/public/audio/Dan_incorrect/Dan-incorrect-2.wav b/client/public/audio/Dan_incorrect/Dan-incorrect-2.wav new file mode 100644 index 0000000..7d999d4 Binary files /dev/null and b/client/public/audio/Dan_incorrect/Dan-incorrect-2.wav differ diff --git a/client/public/audio/Dan_incorrect/Dan-incorrect-3..wav b/client/public/audio/Dan_incorrect/Dan-incorrect-3..wav new file mode 100644 index 0000000..90236e6 Binary files /dev/null and b/client/public/audio/Dan_incorrect/Dan-incorrect-3..wav differ diff --git a/client/public/audio/Dan_incorrect/Dan-incorrect-4.wav b/client/public/audio/Dan_incorrect/Dan-incorrect-4.wav new file mode 100644 index 0000000..d401e24 Binary files /dev/null and b/client/public/audio/Dan_incorrect/Dan-incorrect-4.wav differ diff --git a/client/src/Utils/handleAnswer.ts b/client/src/Utils/handleAnswer.ts new file mode 100644 index 0000000..a13f338 --- /dev/null +++ b/client/src/Utils/handleAnswer.ts @@ -0,0 +1,34 @@ + + +// Picks a random Danism and matching Sound depending on correct/wrong +export function getRandomDanismAndSound(isCorrect: boolean): { text: string, sound: string } { + const correctDanisms = [ + { text: "🔥 Wow. You actually got that right. Weird.", sound: "/audio/Dan_correct/Dan-correct-1.wav" }, + { text: "🎯 Well look who remembered a concept. Congrats.", sound: "/audio/Dan_correct/Dan-correct-2.wav" }, + { text: "🚀 Miracles *do* happen. Mark the calendar.", sound: "/audio/Dan_correct/Dan-correct-3.wav" }, + { text: "🌟 I guess I *have* to give you a star now", sound: "/audio/Dan_correct/Dan-correctStar.wav" }, + ]; + + const incorrectDanisms = [ + { text: "💀 Sure, that's *a* choice. It's just not the right one.", sound: "/audio/Dan_incorrect/Dan-incorrect-1.wav" }, + { text: "😬 Oof. That was bold. Boldly incorrect.", sound: "/audio/Dan_incorrect/Dan-incorrect-2.wav" }, + { text: "🧠 Take a 7-minute break to recover from that disaster.", sound: "/audio/Dan_incorrect/Dan-incorrect-3.wav" }, + { text: "🛑 Fascinating. Bold. Still wrong.", sound: "/audio/Dan_incorrect/Dan-incorrect-4.wav" }, + ]; + + const source = isCorrect ? correctDanisms : incorrectDanisms; + const randomIndex = Math.floor(Math.random() * source.length); + return source[randomIndex]; +} + +// Picks a random Special Legendary Danism (only text) +export function getSpecialDanism(): string { + const specialQuotes = [ + "🌟 Legendary Streak! Even Dr. Dan is impressed... briefly.", + "🌟 5 correct in a row?! You must be an AI in disguise.", + "🌟 Victory streak! Maybe you deserve a 7-minute break after all." + // 👉 Add more special quotes if you want! + ]; + const randomIndex = Math.floor(Math.random() * specialQuotes.length); + return specialQuotes[randomIndex]; +} diff --git a/client/src/Utils/preloadSounds.ts b/client/src/Utils/preloadSounds.ts new file mode 100644 index 0000000..14216ee --- /dev/null +++ b/client/src/Utils/preloadSounds.ts @@ -0,0 +1,7 @@ +export function preloadSounds(paths: string[]): void { + paths.forEach((path) => { + const audio = new Audio(path); + audio.load(); + }); + } + \ No newline at end of file diff --git a/client/src/components/AnswerResultModal.tsx b/client/src/components/AnswerResultModal.tsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/components/DanismEvent.tsx b/client/src/components/DanismEvent.tsx new file mode 100644 index 0000000..d370b28 --- /dev/null +++ b/client/src/components/DanismEvent.tsx @@ -0,0 +1,114 @@ +import { useState, useEffect } from "react"; +import NarrationModal from "@/components/NarrationModal"; +import SoundPlayer from "@/components/SoundPlayer"; +import { preloadSounds } from "../Utils/preloadSounds"; +import { getRandomDanism, getSoundPath } from "../Utils/handleAnswer"; + +const DanismEvent = () => { + const [showModal, setShowModal] = useState(false); + const [audioSrc, setAudioSrc] = useState(null); + const [fallbackAudio, setFallbackAudio] = useState(null); + const [playing, setPlaying] = useState(false); + const [dialogText, setDialogText] = useState(""); + + // 🔥 Preload all audio files once + useEffect(() => { + preloadSounds([ + '/audio/Dan_correct/Dan-correct-1.wav', + '/audio/Dan_correct/Dan-correct-2.wav', + '/audio/Dan_correct/Dan-correct-3.wav', + '/audio/Dan_correct/Dan-correct-4.wav', + '/audio/Dan_incorrect/Dan-incorrect-1.wav', + '/audio/Dan_incorrect/Dan-incorrect-2.wav', + '/audio/Dan_incorrect/Dan-incorrect-3.wav', + '/audio/Dan_incorrect/Dan-incorrect-4.wav', + '/audio/drdan_fallback.mp3' + ]); + }, []); + + // 🔥 Fetch TTS voice from your API when modal opens + useEffect(() => { + if (showModal && dialogText) { + const fetchVoice = async () => { + try { + const res = await fetch("/api/tts", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + text: dialogText, + character: "Dr. Dan", // Character name if your backend needs it + }), + }); + + if (!res.ok) throw new Error("TTS API failed"); + + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + setAudioSrc(url); // TTS Audio ready + setPlaying(true); + } catch (err) { + console.error("TTS Error:", err); + // fallback to local mp3 if TTS fails + setFallbackAudio("/audio/drdan_fallback.mp3"); + setPlaying(true); + } + }; + + fetchVoice(); + } + }, [showModal, dialogText]); + + // 🔥 Handle when player answers something + const triggerDanism = (isCorrect: boolean) => { + setDialogText(getRandomDanism(isCorrect)); // Pick random quote + setAudioSrc(getSoundPath(isCorrect)); // Play random correct/wrong sound immediately + setFallbackAudio(null); // Reset fallback + setShowModal(true); // Open modal + setPlaying(true); // Start playing + }; + + return ( + <> + {/* TEMP TEST BUTTONS */} +
+ + +
+ + {/* 🧬 Narration Modal */} + setShowModal(false)} + text={dialogText} + /> + + {/* 🎶 SoundPlayer */} + {audioSrc && ( + setPlaying(false)} + /> + )} + {fallbackAudio && !audioSrc && ( + setPlaying(false)} + /> + )} + + ); +}; + +export default DanismEvent; diff --git a/client/src/components/MinionModal.tsx b/client/src/components/MinionModal.tsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/components/NarrationModal.tsx b/client/src/components/NarrationModal.tsx index e60fb14..f7bd460 100644 --- a/client/src/components/NarrationModal.tsx +++ b/client/src/components/NarrationModal.tsx @@ -1,8 +1,4 @@ -// src/components/NarrationModal.tsx - -"use client"; - -import ModalBase from "@/components/ModalBase"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; interface NarrationModalProps { isOpen: boolean; @@ -12,16 +8,17 @@ interface NarrationModalProps { const NarrationModal = ({ isOpen, onClose, text }: NarrationModalProps) => { return ( - { if (!open) onClose(); }} - title="Narration" - description={text} - > - {/* Optional: You can add more children here later if needed */} - + { if (!open) onClose(); }}> + + + {text} + + + {/* Optional: you can add subtext, instructions, even a button here */} + + + ); }; export default NarrationModal; - diff --git a/client/src/components/ResponseModal.tsx b/client/src/components/ResponseModal.tsx deleted file mode 100644 index da45528..0000000 --- a/client/src/components/ResponseModal.tsx +++ /dev/null @@ -1,23 +0,0 @@ -"use client" - -import ModalBase from "@/components/ModalBase"; - -interface ResponseModalProps { - isOpen: boolean; - onClose: () => void; - text: string; - -} - -const ResponseModal = ({ isOpen, onClose, text }: ResponseModalProps) => { - return ( - { if(!open) onClose(); }} - title="Narration" - description={text} - > - - ); -}; -export default ResponseModal; \ No newline at end of file diff --git a/client/src/components/SoundPlayer.tsx b/client/src/components/SoundPlayer.tsx new file mode 100644 index 0000000..46ee933 --- /dev/null +++ b/client/src/components/SoundPlayer.tsx @@ -0,0 +1,22 @@ +// src/components/SoundPlayer.tsx +import * as React from 'react'; +import ReactHowler from 'react-howler'; + +interface SoundPlayerProps { + src: string; + playing: boolean; + onEnd?: () => void; +} + +const SoundPlayer: React.FC = ({ src, playing, onEnd }) => { + return ( + + ); +}; + +export default SoundPlayer; diff --git a/client/src/components/screens/GameMap.tsx b/client/src/components/screens/GameMap.tsx index cc8400e..539f283 100644 --- a/client/src/components/screens/GameMap.tsx +++ b/client/src/components/screens/GameMap.tsx @@ -1,4 +1,11 @@ + +// src/components/screens/GameMap.tsx + +// IMPORT LIBRARIES +import * as React from 'react'; + import React, { useState } from 'react'; + import { useNavigate } from 'react-router-dom'; import Minion from './Minions'; import "../../styles/codezilla.css"; diff --git a/client/src/graphql/mutations.ts b/client/src/graphql/mutations.ts new file mode 100644 index 0000000..9c840a7 --- /dev/null +++ b/client/src/graphql/mutations.ts @@ -0,0 +1,10 @@ +import { gql } from '@apollo/client'; + +export const SUBMIT_ANSWER = gql` + mutation SubmitAnswer($questionId: ID!, $selectedOption: String!) { + submitAnswer(questionId: $questionId, selectedOption: $selectedOption) { + isCorrect + feedback + } + } +`; diff --git a/client/src/graphql/queries.ts b/client/src/graphql/queries.ts new file mode 100644 index 0000000..4b76e20 --- /dev/null +++ b/client/src/graphql/queries.ts @@ -0,0 +1,13 @@ +import { gql } from '@apollo/client'; + +export const GET_QUESTIONS = gql` + query GetQuestions { + questions { + _id + questionText + options + correctAnswer + } + } +`; + diff --git a/client/src/main.tsx b/client/src/main.tsx index e64bf96..b42630c 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -2,11 +2,13 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import './index.css'; +import { ApolloProvider } from '@apollo/client'; +import client from './Utils/apolloClient'; // or wherever your client file is -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); - -root.render( +ReactDOM.createRoot(document.getElementById('root')!).render( - - + + + + , ); diff --git a/client/src/pages/CodezillaArena.tsx b/client/src/pages/CodezillaArena.tsx index 2471813..d529067 100644 --- a/client/src/pages/CodezillaArena.tsx +++ b/client/src/pages/CodezillaArena.tsx @@ -1,84 +1,130 @@ -import { useState, useEffect } from 'react'; -import { - Dialog, - DialogTrigger, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +// import { useEffect, useState } from "react"; +// import { preloadSounds } from "../Utils/preloadSounds"; +// import { +// Dialog, +// DialogTrigger, +// DialogContent, +// DialogHeader, +// DialogTitle, +// } from "@/components/ui/dialog"; +// import { getRandomDanism, getSoundPath } from "../Utils/handleAnswer"; +// import SoundPlayer from "@/components/SoundPlayer"; -const CodezillaArena = () => { - const [showModal, setShowModal] = useState(false); - const [audioSrc, setAudioSrc] = useState(null); - const [dialogText, setDialogText] = useState( - "Take a 7-minute break to recover from that disaster." - ); +// const CodezillaArena = () => { +// const [showModal, setShowModal] = useState(false); +// const [audioSrc, setAudioSrc] = useState(null); +// const [playing, setPlaying] = useState(false); +// const [dialogText, setDialogText] = useState(""); +// const [fallbackAudio, setFallbackAudio] = useState(null); - useEffect(() => { - if (showModal) { - const fetchVoice = async () => { - try { - const res = await fetch("/api/tts", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - text: dialogText, - character: "Dr. Dan", // Make sure backend maps this to "shimmer" - }), - }); +// // 👉 PRELOAD ALL YOUR AUDIO FILES ONCE +// useEffect(() => { +// preloadSounds([ +// '/audio/Dan_correct/Dan-correct-1.wav', +// '/audio/Dan_correct/Dan-correct-2.wav', +// '/audio/Dan_correct/Dan-correct-3.wav', +// '/audio/Dan_correct/Dan-correct-4.wav', +// '/audio/Dan_incorrect/Dan-incorrect-1.wav', +// '/audio/Dan_incorrect/Dan-incorrect-2.wav', +// '/audio/Dan_incorrect/Dan-incorrect-3.wav', +// '/audio/Dan_incorrect/Dan-incorrect-4.wav', +// '/audio/drdan_fallback.mp3' // also preload fallback voice +// ]); +// }, []); - if (!res.ok) throw new Error("TTS API failed"); +// useEffect(() => { +// if (showModal && dialogText) { +// const fetchVoice = async () => { +// try { +// const res = await fetch("/api/tts", { +// method: "POST", +// headers: { "Content-Type": "application/json" }, +// body: JSON.stringify({ +// text: dialogText, +// character: "Dr. Dan", +// }), +// }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); - setAudioSrc(url); +// if (!res.ok) throw new Error("TTS API failed"); - const audio = new Audio(url); - audio.play(); - } catch (err) { - console.error("TTS Error:", err); - const fallback = new Audio("/audio/drdan_fallback.mp3"); - fallback.play(); - } - }; +// const blob = await res.blob(); +// const url = URL.createObjectURL(blob); +// setAudioSrc(url); +// setPlaying(true); +// } catch (err) { +// console.error("TTS Error:", err); +// setFallbackAudio("/audio/drdan_fallback.mp3"); +// setPlaying(true); +// } +// }; - fetchVoice(); - } - }, [showModal, dialogText]); +// fetchVoice(); +// } +// }, [showModal, dialogText]); - return ( -
-
-

- 🧠 Codezilla Arena (Prototype Mode) -

+// const handleAnswer = (isCorrect: boolean) => { +// setDialogText(getRandomDanism(isCorrect)); +// setAudioSrc(getSoundPath(isCorrect)); // Play correct/wrong sound while TTS loads +// setShowModal(true); +// setPlaying(true); +// }; - - - - +// return ( +//
+//
+//

+// 🧠 Codezilla Arena (Prototype Mode) +//

- - - 🧬 Dr. Dan Speaks - -

- {dialogText} -

- {audioSrc &&
-
-
-
- ); -}; +//
+// +// +//
-export default CodezillaArena; +// +// +// +// + +// +// +// 🧬 Dr. Dan Speaks +// +//

+// {dialogText} +//

+ +// {audioSrc && ( +// setPlaying(false)} +// /> +// )} +// {fallbackAudio && !audioSrc && ( +// setPlaying(false)} +// /> +// )} +//
+//
+// +// +// ); +// }; + +// export default CodezillaArena; diff --git a/package-lock.json b/package-lock.json index ba8c698..10822e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3298,13 +3298,7 @@ "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" } - }, - "node_modules/tailwindcss": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", - "integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==", - "dev": true, - "license": "MIT" + }, "node_modules/tar": { "version": "6.2.1", @@ -3323,6 +3317,13 @@ "node": ">=10" } }, + "node_modules/tailwindcss": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", + "integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/server/seed.ts b/server/seed.ts new file mode 100644 index 0000000..b6a8c75 --- /dev/null +++ b/server/seed.ts @@ -0,0 +1,29 @@ +import mongoose from "mongoose"; +import dotenv from "dotenv"; +import User from "../server/src/models/User"; +import { Question } from "../server/src/models/Questions"; + +dotenv.config(); + +async function seedDatabase() { + await mongoose.connect(process.env.MONGODB_URI!); + + // Wipe collections + await User.deleteMany({}); + await Question.deleteMany({}); + + // Seed data + await User.create({ username: "playerOne", email: "player@example.com", password: "123456" }); + await Question.insertMany([ + { prompt: "Explain closures in JavaScript", difficulty: "hard" }, + { prompt: "What is a component in React?", difficulty: "easy" } + ]); + + console.log("🌱 Seeding complete!"); + mongoose.connection.close(); +} + +seedDatabase().catch((error) => { + console.error("Error seeding database:", error); + mongoose.connection.close(); +}); diff --git a/server/src/utils/PromptBuilder.ts b/server/src/utils/PromptBuilder.ts index 36af986..a5c81b1 100644 --- a/server/src/utils/PromptBuilder.ts +++ b/server/src/utils/PromptBuilder.ts @@ -1,4 +1,4 @@ -import { FallbackQuestion } from "@/utils/fallbackQuestions"; +import { FallbackQuestion, fallbackQuestion } from "@/utils/fallbackQuestions"; export class PromptBuilder { /** @@ -144,7 +144,7 @@ Correct Answer: ... * If the LLM API fails, pick a random fallback question */ static getFallbackQuestion(minionName: string): FallbackQuestion { - const pool = fallbackQuestions[minionName] || []; + const pool = fallbackQuestion[minionName] || []; const idx = Math.floor(Math.random() * pool.length); return pool[idx]; } @@ -153,23 +153,28 @@ Correct Answer: ... /** * Parses the raw OpenAI response into question, choices, and answer */ -export function parseOpenAIResponse(raw: string) { - const lines = raw.split("\n").map(line => line.trim()).filter(Boolean); - - let question = ""; - const choices: string[] = []; - let answer = ""; - - for (const line of lines) { - if (/^question:/i.test(line)) { - question = line.replace(/^question:\s*/i, ""); - } else if (/^[A-D]\)/.test(line)) { - choices.push(line); - } else if (/^correct answer:/i.test(line)) { - const match = line.match(/[A-D]/i); - if (match) answer = match[0].toUpperCase(); - } +// Removed duplicate implementation of parseOpenAIResponse to resolve redeclaration error. + + export function parseOpenAIResponse(raw: string) { + const lines = raw.split('\n').map(line => line.trim()).filter(line => line !== ''); + + let question = ''; + const choices: string[] = []; + let answer = ''; + + for (const line of lines) { + if (line.toLowerCase().startsWith('question:')) { + question = line.replace(/^question:\s*/i, '').trim(); + } else if (/^[a-dA-D]\)/.test(line)) { + choices.push(line); + } else if (line.toLowerCase().startsWith('correct answer')) { + const match = line.match(/[A-D]/i); + if (match) { + answer = match[0].toUpperCase(); + } + } + } + + return { question, choices, answer }; } - - return { question, choices, answer }; -} + \ No newline at end of file