diff --git a/Dockerfile b/Dockerfile index 974acca..cb58f84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,9 @@ RUN pnpm install --frozen-lockfile # Build Next.js app with standalone output for smaller runtime image WORKDIR /app/packages/web + +ENV NEXT_TELEMETRY_DISABLED=1 + RUN pnpm build # Build socket server if needed (TypeScript or similar) @@ -56,6 +59,9 @@ COPY --from=builder /app/packages/web/public ./packages/web/public # Copy the socket server build COPY --from=builder /app/packages/socket/dist ./packages/socket/dist +# Copy the game default config +COPY --from=builder /app/config ./config + # Expose the web and socket ports EXPOSE 3000 5505 diff --git a/package.json b/package.json index b5f5cf8..e173a4a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev:socket": "dotenv -e .env -- pnpm --filter socket dev", "build": "pnpm -r run build", "start": "pnpm -r --parallel run start", - "clean": "pnpm -r exec rm -rf dist node_modules" + "clean": "pnpm -r exec rm -rf dist node_modules", + "lint": "pnpm -r run lint" }, "devDependencies": { "dotenv-cli": "^10.0.0", diff --git a/packages/common/eslint.config.mjs b/packages/common/eslint.config.mjs new file mode 100644 index 0000000..648d4db --- /dev/null +++ b/packages/common/eslint.config.mjs @@ -0,0 +1,208 @@ +import js from "@eslint/js" +import { defineConfig } from "eslint/config" +import globals from "globals" +import tseslint from "typescript-eslint" + +export default defineConfig([ + { + ignores: ["**/node_modules/**"], + }, + { + files: ["**/*.ts"], + languageOptions: { + ...js.configs.recommended.languageOptions, + parser: tseslint.parser, + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + globals: { + ...globals.node, + }, + }, + plugins: { + "@typescript-eslint": tseslint.plugin, + }, + rules: { + ...js.configs.recommended.rules, + ...tseslint.configs.recommendedTypeChecked[0].rules, + + "array-callback-return": [ + "error", + { allowImplicit: false, checkForEach: true, allowVoid: true }, + ], + "no-await-in-loop": "error", + "no-constant-binary-expression": "error", + "no-constructor-return": "error", + "no-duplicate-imports": ["error", { includeExports: true }], + "no-new-native-nonconstructor": "error", + "no-promise-executor-return": ["error", { allowVoid: true }], + "no-self-compare": "error", + "no-template-curly-in-string": "error", + "no-unmodified-loop-condition": "error", + "no-unreachable-loop": "error", + "no-unused-private-class-members": "error", + "arrow-body-style": ["error", "as-needed"], + camelcase: [ + "error", + { + properties: "always", + ignoreDestructuring: true, + ignoreImports: true, + ignoreGlobals: true, + }, + ], + "capitalized-comments": [ + "error", + "always", + { ignoreConsecutiveComments: true }, + ], + "class-methods-use-this": ["error", { enforceForClassFields: true }], + complexity: ["warn", 40], + "consistent-return": "error", + curly: ["error", "all"], + "default-param-last": "error", + "dot-notation": "error", + eqeqeq: ["error", "always"], + "func-name-matching": "error", + "func-names": "error", + "func-style": ["error", "declaration", { allowArrowFunctions: true }], + "grouped-accessor-pairs": ["error", "getBeforeSet"], + "guard-for-in": "error", + "init-declarations": ["error", "always"], + "logical-assignment-operators": [ + "error", + "always", + { enforceForIfStatements: true }, + ], + "max-classes-per-file": ["error", { ignoreExpressions: true }], + "max-depth": ["error", 3], + "max-lines": [ + "error", + { max: 500, skipBlankLines: true, skipComments: true }, + ], + "max-nested-callbacks": ["error", 3], + "max-params": ["error", 4], + "multiline-comment-style": ["error", "separate-lines"], + "no-alert": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-else-return": "error", + "no-empty-function": "error", + "no-empty-static-block": "error", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-label": "error", + "no-implicit-coercion": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "error", + "no-invalid-this": "error", + "no-iterator": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-multi-assign": "error", + "no-multi-str": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-wrappers": "error", + "no-object-constructor": "error", + "no-octal-escape": "error", + "no-param-reassign": "error", + "no-plusplus": "error", + "no-proto": "error", + "no-return-assign": ["error", "always"], + "no-script-url": "error", + "no-sequences": "error", + "no-throw-literal": "error", + "no-undef-init": "error", + "no-unneeded-ternary": ["error", { defaultAssignment: false }], + "no-unused-expressions": ["error", { enforceForJSX: true }], + "no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "no-useless-call": "error", + "no-useless-computed-key": ["error", { enforceForClassMembers: true }], + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "error", + "no-warning-comments": ["error", { terms: ["todo"] }], + "object-shorthand": ["error", "always"], + "one-var": ["error", "never"], + "operator-assignment": ["error", "always"], + "prefer-arrow-callback": "error", + "prefer-const": [ + "error", + { destructuring: "any", ignoreReadBeforeAssign: false }, + ], + "prefer-destructuring": "error", + "prefer-exponentiation-operator": "error", + "prefer-numeric-literals": "error", + "prefer-object-has-own": "error", + "prefer-object-spread": "error", + "prefer-promise-reject-errors": "error", + "prefer-regex-literals": ["error", { disallowRedundantWrapping: true }], + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + radix: "error", + "require-await": "error", + "require-unicode-regexp": "error", + "symbol-description": "error", + yoda: "error", + "line-comment-position": ["error", { position: "above" }], + indent: "off", + "newline-before-return": "error", + "no-undef": "error", + "padded-blocks": ["error", "never"], + "padding-line-between-statements": [ + "error", + { + blankLine: "always", + prev: "*", + next: [ + "break", + "case", + "cjs-export", + "class", + "continue", + "do", + "if", + "switch", + "try", + "while", + "return", + ], + }, + { + blankLine: "always", + prev: [ + "break", + "case", + "cjs-export", + "class", + "continue", + "do", + "if", + "switch", + "try", + "while", + "return", + ], + next: "*", + }, + ], + quotes: [ + "error", + "double", + { avoidEscape: true, allowTemplateLiterals: true }, + ], + "space-before-blocks": "error", + semi: ["error", "never"], + }, + }, +]) diff --git a/packages/common/package.json b/packages/common/package.json index be7ddc7..f9f4b6f 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -2,12 +2,19 @@ "name": "@rahoot/common", "version": "1.0.0", "type": "module", + "scripts": { + "lint": "eslint" + }, "dependencies": { "socket.io": "^4.8.1", - "zod": "^3.22.4" + "zod": "^3.25.76" }, "devDependencies": { - "@types/node": "^20.11.0", - "typescript": "^5.3.3" + "@eslint/js": "^9.38.0", + "@types/node": "^20.19.22", + "eslint": "^9.38.0", + "globals": "^16.4.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.46.1" } } diff --git a/packages/common/src/types/game/socket.ts b/packages/common/src/types/game/socket.ts index fb0d4e1..e0b5294 100644 --- a/packages/common/src/types/game/socket.ts +++ b/packages/common/src/types/game/socket.ts @@ -24,65 +24,65 @@ export interface ServerToClientEvents { connect: () => void // Game events - "game:status": (data: { name: Status; data: StatusDataMap[Status] }) => void - "game:successRoom": (data: string) => void - "game:successJoin": (gameId: string) => void - "game:totalPlayers": (count: number) => void - "game:errorMessage": (message: string) => void + "game:status": (_data: { name: Status; data: StatusDataMap[Status] }) => void + "game:successRoom": (_data: string) => void + "game:successJoin": (_gameId: string) => void + "game:totalPlayers": (_count: number) => void + "game:errorMessage": (_message: string) => void "game:startCooldown": () => void - "game:cooldown": (count: number) => void + "game:cooldown": (_count: number) => void "game:kick": () => void "game:reset": () => void - "game:updateQuestion": (data: { current: number; total: number }) => void - "game:playerAnswer": (count: number) => void + "game:updateQuestion": (_data: { current: number; total: number }) => void + "game:playerAnswer": (_count: number) => void // Player events - "player:successReconnect": (data: { + "player:successReconnect": (_data: { gameId: string status: { name: Status; data: StatusDataMap[Status] } player: { username: string; points: number } currentQuestion: GameUpdateQuestion }) => void - "player:updateLeaderboard": (data: { leaderboard: Player[] }) => void + "player:updateLeaderboard": (_data: { leaderboard: Player[] }) => void // Manager events - "manager:successReconnect": (data: { + "manager:successReconnect": (_data: { gameId: string status: { name: Status; data: StatusDataMap[Status] } players: Player[] currentQuestion: GameUpdateQuestion }) => void - "manager:quizzList": (quizzList: QuizzWithId[]) => void - "manager:gameCreated": (data: { gameId: string; inviteCode: string }) => void - "manager:statusUpdate": (data: { + "manager:quizzList": (_quizzList: QuizzWithId[]) => void + "manager:gameCreated": (_data: { gameId: string; inviteCode: string }) => void + "manager:statusUpdate": (_data: { status: Status data: StatusDataMap[Status] }) => void - "manager:newPlayer": (player: Player) => void - "manager:removePlayer": (playerId: string) => void - "manager:errorMessage": (message: string) => void - "manager:playerKicked": (playerId: string) => void + "manager:newPlayer": (_player: Player) => void + "manager:removePlayer": (_playerId: string) => void + "manager:errorMessage": (_message: string) => void + "manager:playerKicked": (_playerId: string) => void } export interface ClientToServerEvents { // Manager actions - "game:create": (quizzId: string) => void - "manager:auth": (password: string) => void - "manager:reconnect": (message: { gameId: string }) => void + "game:create": (_quizzId: string) => void + "manager:auth": (_password: string) => void + "manager:reconnect": (_message: { gameId: string }) => void "manager:kickPlayer": ( - message: MessageWithoutStatus<{ playerId: string }> + _message: MessageWithoutStatus<{ playerId: string }> ) => void - "manager:startGame": (message: MessageGameId) => void - "manager:abortQuiz": (message: MessageGameId) => void - "manager:nextQuestion": (message: MessageGameId) => void - "manager:showLeaderboard": (message: MessageGameId) => void + "manager:startGame": (_message: MessageGameId) => void + "manager:abortQuiz": (_message: MessageGameId) => void + "manager:nextQuestion": (_message: MessageGameId) => void + "manager:showLeaderboard": (_message: MessageGameId) => void // Player actions - "player:join": (inviteCode: string) => void - "player:login": (message: MessageWithoutStatus<{ username: string }>) => void - "player:reconnect": (message: { gameId: string }) => void + "player:join": (_inviteCode: string) => void + "player:login": (_message: MessageWithoutStatus<{ username: string }>) => void + "player:reconnect": (_message: { gameId: string }) => void "player:selectedAnswer": ( - message: MessageWithoutStatus<{ answerKey: number }> + _message: MessageWithoutStatus<{ answerKey: number }> ) => void // Common diff --git a/packages/common/src/types/game/status.ts b/packages/common/src/types/game/status.ts index dfcc5b2..3a73868 100644 --- a/packages/common/src/types/game/status.ts +++ b/packages/common/src/types/game/status.ts @@ -1,18 +1,19 @@ -import { Player } from "."; +import { Player } from "." -export enum Status { - SHOW_ROOM = "SHOW_ROOM", - SHOW_START = "SHOW_START", - SHOW_PREPARED = "SHOW_PREPARED", - SHOW_QUESTION = "SHOW_QUESTION", - SELECT_ANSWER = "SELECT_ANSWER", - SHOW_RESULT = "SHOW_RESULT", - SHOW_RESPONSES = "SHOW_RESPONSES", - SHOW_LEADERBOARD = "SHOW_LEADERBOARD", - FINISHED = "FINISHED", - WAIT = "WAIT", -} +export const STATUS = { + SHOW_ROOM: "SHOW_ROOM", + SHOW_START: "SHOW_START", + SHOW_PREPARED: "SHOW_PREPARED", + SHOW_QUESTION: "SHOW_QUESTION", + SELECT_ANSWER: "SELECT_ANSWER", + SHOW_RESULT: "SHOW_RESULT", + SHOW_RESPONSES: "SHOW_RESPONSES", + SHOW_LEADERBOARD: "SHOW_LEADERBOARD", + FINISHED: "FINISHED", + WAIT: "WAIT", +} as const +export type Status = (typeof STATUS)[keyof typeof STATUS] export type CommonStatusDataMap = { SHOW_START: { time: number; subject: string } @@ -49,10 +50,6 @@ type ManagerExtraStatus = { SHOW_LEADERBOARD: { leaderboard: Player[] } } -type PlayerExtraStatus = { - WAIT: { text: string } -} - -export type PlayerStatusDataMap = CommonStatusDataMap & PlayerExtraStatus +export type PlayerStatusDataMap = CommonStatusDataMap export type ManagerStatusDataMap = CommonStatusDataMap & ManagerExtraStatus export type StatusDataMap = PlayerStatusDataMap & ManagerStatusDataMap diff --git a/packages/socket/src/utils/validator.ts b/packages/common/src/validators/auth.ts similarity index 100% rename from packages/socket/src/utils/validator.ts rename to packages/common/src/validators/auth.ts diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index 3eb1a0e..42ed9cb 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -1,4 +1,5 @@ { + "extends": "../../tsconfig.json", "compilerOptions": { "target": "ESNext", "module": "ESNext", diff --git a/packages/config/game.json b/packages/config/game.json new file mode 100644 index 0000000..66f12d9 --- /dev/null +++ b/packages/config/game.json @@ -0,0 +1,4 @@ +{ + "managerPassword": "PASSWORD", + "music": true +} \ No newline at end of file diff --git a/packages/config/quizz/example.json b/packages/config/quizz/example.json new file mode 100644 index 0000000..c048d43 --- /dev/null +++ b/packages/config/quizz/example.json @@ -0,0 +1,41 @@ +{ + "subject": "Example Quizz", + "questions": [ + { + "question": "What is good answer ?", + "answers": [ + "No", + "Good answer", + "No", + "No" + ], + "solution": 1, + "cooldown": 5, + "time": 15 + }, + { + "question": "What is good answer with image ?", + "answers": [ + "No", + "No", + "No", + "Good answer" + ], + "image": "https://placehold.co/600x400.png", + "solution": 3, + "cooldown": 5, + "time": 20 + }, + { + "question": "What is good answer with two answers ?", + "answers": [ + "Good answer", + "No" + ], + "image": "https://placehold.co/600x400.png", + "solution": 0, + "cooldown": 5, + "time": 20 + } + ] +} \ No newline at end of file diff --git a/packages/socket/eslint.config.mjs b/packages/socket/eslint.config.mjs new file mode 100644 index 0000000..297a7d1 --- /dev/null +++ b/packages/socket/eslint.config.mjs @@ -0,0 +1,208 @@ +import js from "@eslint/js" +import { defineConfig } from "eslint/config" +import globals from "globals" +import tseslint from "typescript-eslint" + +export default defineConfig([ + { + ignores: ["**/node_modules/**", "**/dist/**"], + }, + { + files: ["**/*.ts"], + languageOptions: { + ...js.configs.recommended.languageOptions, + parser: tseslint.parser, + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + globals: { + ...globals.node, + }, + }, + plugins: { + "@typescript-eslint": tseslint.plugin, + }, + rules: { + ...js.configs.recommended.rules, + ...tseslint.configs.recommendedTypeChecked[0].rules, + + "array-callback-return": [ + "error", + { allowImplicit: false, checkForEach: true, allowVoid: true }, + ], + "no-await-in-loop": "error", + "no-constant-binary-expression": "error", + "no-constructor-return": "error", + "no-duplicate-imports": ["error", { includeExports: true }], + "no-new-native-nonconstructor": "error", + "no-promise-executor-return": ["error", { allowVoid: true }], + "no-self-compare": "error", + "no-template-curly-in-string": "error", + "no-unmodified-loop-condition": "error", + "no-unreachable-loop": "error", + "no-unused-private-class-members": "error", + "arrow-body-style": ["error", "as-needed"], + camelcase: [ + "error", + { + properties: "always", + ignoreDestructuring: true, + ignoreImports: true, + ignoreGlobals: true, + }, + ], + "capitalized-comments": [ + "error", + "always", + { ignoreConsecutiveComments: true }, + ], + "class-methods-use-this": ["error", { enforceForClassFields: true }], + complexity: ["warn", 40], + "consistent-return": "error", + curly: ["error", "all"], + "default-param-last": "error", + "dot-notation": "error", + eqeqeq: ["error", "always"], + "func-name-matching": "error", + "func-names": "error", + "func-style": ["error", "declaration", { allowArrowFunctions: true }], + "grouped-accessor-pairs": ["error", "getBeforeSet"], + "guard-for-in": "error", + "init-declarations": ["error", "always"], + "logical-assignment-operators": [ + "error", + "always", + { enforceForIfStatements: true }, + ], + "max-classes-per-file": ["error", { ignoreExpressions: true }], + "max-depth": ["error", 3], + "max-lines": [ + "error", + { max: 500, skipBlankLines: true, skipComments: true }, + ], + "max-nested-callbacks": ["error", 3], + "max-params": ["error", 4], + "multiline-comment-style": ["error", "separate-lines"], + "no-alert": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-else-return": "error", + "no-empty-function": "error", + "no-empty-static-block": "error", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-label": "error", + "no-implicit-coercion": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "error", + "no-invalid-this": "error", + "no-iterator": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-multi-assign": "error", + "no-multi-str": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-wrappers": "error", + "no-object-constructor": "error", + "no-octal-escape": "error", + "no-param-reassign": "error", + "no-plusplus": "error", + "no-proto": "error", + "no-return-assign": ["error", "always"], + "no-script-url": "error", + "no-sequences": "error", + "no-throw-literal": "error", + "no-undef-init": "error", + "no-unneeded-ternary": ["error", { defaultAssignment: false }], + "no-unused-expressions": ["error", { enforceForJSX: true }], + "no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "no-useless-call": "error", + "no-useless-computed-key": ["error", { enforceForClassMembers: true }], + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "error", + "no-warning-comments": ["error", { terms: ["todo"] }], + "object-shorthand": ["error", "always"], + "one-var": ["error", "never"], + "operator-assignment": ["error", "always"], + "prefer-arrow-callback": "error", + "prefer-const": [ + "error", + { destructuring: "any", ignoreReadBeforeAssign: false }, + ], + "prefer-destructuring": "error", + "prefer-exponentiation-operator": "error", + "prefer-numeric-literals": "error", + "prefer-object-has-own": "error", + "prefer-object-spread": "error", + "prefer-promise-reject-errors": "error", + "prefer-regex-literals": ["error", { disallowRedundantWrapping: true }], + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + radix: "error", + "require-await": "error", + "require-unicode-regexp": "error", + "symbol-description": "error", + yoda: "error", + "line-comment-position": ["error", { position: "above" }], + indent: "off", + "newline-before-return": "error", + "no-undef": "error", + "padded-blocks": ["error", "never"], + "padding-line-between-statements": [ + "error", + { + blankLine: "always", + prev: "*", + next: [ + "break", + "case", + "cjs-export", + "class", + "continue", + "do", + "if", + "switch", + "try", + "while", + "return", + ], + }, + { + blankLine: "always", + prev: [ + "break", + "case", + "cjs-export", + "class", + "continue", + "do", + "if", + "switch", + "try", + "while", + "return", + ], + next: "*", + }, + ], + quotes: [ + "error", + "double", + { avoidEscape: true, allowTemplateLiterals: true }, + ], + "space-before-blocks": "error", + semi: ["error", "never"], + }, + }, +]) diff --git a/packages/socket/package.json b/packages/socket/package.json index f9b4205..8edcf74 100644 --- a/packages/socket/package.json +++ b/packages/socket/package.json @@ -5,7 +5,8 @@ "scripts": { "dev": "tsx watch src/index.ts", "build": "node esbuild.config.js", - "start": "node dist/index.cjs" + "start": "node dist/index.cjs", + "lint": "eslint" }, "keywords": [], "author": "", @@ -13,12 +14,18 @@ "dependencies": { "@rahoot/common": "workspace:*", "@t3-oss/env-core": "^0.13.8", + "dayjs": "^1.11.18", "socket.io": "^4.8.1", "uuid": "^13.0.0", "zod": "^4.1.11" }, "devDependencies": { + "@eslint/js": "^9.38.0", + "@types/node": "^24.8.1", "esbuild": "^0.25.10", - "tsx": "^4.20.6" + "eslint": "^9", + "globals": "^16.4.0", + "tsx": "^4.20.6", + "typescript-eslint": "^8.46.1" } } diff --git a/packages/socket/src/index.ts b/packages/socket/src/index.ts index d784de6..646831e 100644 --- a/packages/socket/src/index.ts +++ b/packages/socket/src/index.ts @@ -1,30 +1,28 @@ import { Server } from "@rahoot/common/types/game/socket" +import { inviteCodeValidator } from "@rahoot/common/validators/auth" import env from "@rahoot/socket/env" import Config from "@rahoot/socket/services/config" import Game from "@rahoot/socket/services/game" -import { - findManagerGameByClientId, - findPlayerGameByClientId, - withGame, -} from "@rahoot/socket/utils/game" -import { inviteCodeValidator } from "@rahoot/socket/utils/validator" +import Registry from "@rahoot/socket/services/registry" +import { withGame } from "@rahoot/socket/utils/game" import { Server as ServerIO } from "socket.io" const io: Server = new ServerIO() Config.init() -let games: Game[] = [] +const registry = Registry.getInstance() const port = env.SOCKER_PORT || 3001 console.log(`Socket server running on port ${port}`) io.listen(Number(port)) io.on("connection", (socket) => { - console.log(`A user connected ${socket.id}`) - console.log(socket.handshake.auth) + console.log( + `A user connected: socketId: ${socket.id}, clientId: ${socket.handshake.auth.clientId}` + ) socket.on("player:reconnect", () => { - const game = findPlayerGameByClientId(socket.handshake.auth.clientId, games) + const game = registry.getPlayerGame(socket.handshake.auth.clientId) if (game) { game.reconnect(socket) @@ -36,13 +34,11 @@ io.on("connection", (socket) => { }) socket.on("manager:reconnect", () => { - const game = findManagerGameByClientId( - socket.handshake.auth.clientId, - games - ) + const game = registry.getManagerGame(socket.handshake.auth.clientId) if (game) { game.reconnect(socket) + registry.reactivateGame(game.gameId) return } @@ -56,6 +52,7 @@ io.on("connection", (socket) => { if (password !== config.managerPassword) { socket.emit("manager:errorMessage", "Invalid password") + return } @@ -72,23 +69,24 @@ io.on("connection", (socket) => { if (!quizz) { socket.emit("game:errorMessage", "Quizz not found") + return } const game = new Game(io, socket, quizz) - - games.push(game) + registry.addGame(game) }) - socket.on("player:join", async (inviteCode) => { + socket.on("player:join", (inviteCode) => { const result = inviteCodeValidator.safeParse(inviteCode) if (result.error) { socket.emit("game:errorMessage", result.error.issues[0].message) + return } - const game = games.find((g) => g.inviteCode === inviteCode) + const game = registry.getGameByInviteCode(inviteCode) if (!game) { socket.emit("game:errorMessage", "Game not found") @@ -100,53 +98,54 @@ io.on("connection", (socket) => { }) socket.on("player:login", ({ gameId, data }) => - withGame(gameId, socket, games, (game) => game.join(socket, data.username)) + withGame(gameId, socket, (game) => game.join(socket, data.username)) ) socket.on("manager:kickPlayer", ({ gameId, data }) => - withGame(gameId, socket, games, (game) => - game.kickPlayer(socket, data.playerId) - ) + withGame(gameId, socket, (game) => game.kickPlayer(socket, data.playerId)) ) socket.on("manager:startGame", ({ gameId }) => - withGame(gameId, socket, games, (game) => game.start(socket)) + withGame(gameId, socket, (game) => game.start(socket)) ) socket.on("player:selectedAnswer", ({ gameId, data }) => - withGame(gameId, socket, games, (game) => + withGame(gameId, socket, (game) => game.selectAnswer(socket, data.answerKey) ) ) socket.on("manager:abortQuiz", ({ gameId }) => - withGame(gameId, socket, games, (game) => game.abortRound(socket)) + withGame(gameId, socket, (game) => game.abortRound(socket)) ) socket.on("manager:nextQuestion", ({ gameId }) => - withGame(gameId, socket, games, (game) => game.nextRound(socket)) + withGame(gameId, socket, (game) => game.nextRound(socket)) ) socket.on("manager:showLeaderboard", ({ gameId }) => - withGame(gameId, socket, games, (game) => game.showLeaderboard()) + withGame(gameId, socket, (game) => game.showLeaderboard()) ) socket.on("disconnect", () => { console.log(`user disconnected ${socket.id}`) - const managerGame = games.find((g) => g.manager.id === socket.id) - if (managerGame && !managerGame.started) { - console.log("Reset game (manager disconnected)") + const managerGame = registry.getGameByManagerSocketId(socket.id) - managerGame.abortCooldown() - io.to(managerGame.gameId).emit("game:reset") + if (managerGame) { + registry.markGameAsEmpty(managerGame) - games = games.filter((g) => g.gameId !== managerGame.gameId) + if (!managerGame.started) { + console.log("Reset game (manager disconnected)") + managerGame.abortCooldown() + io.to(managerGame.gameId).emit("game:reset") + registry.removeGame(managerGame.gameId) - return + return + } } - const game = games.find((g) => g.players.some((p) => p.id === socket.id)) + const game = registry.getGameByPlayerSocketId(socket.id) if (!game || game.started) { return @@ -166,3 +165,13 @@ io.on("connection", (socket) => { console.log(`Removed player ${player.username} from game ${game.gameId}`) }) }) + +process.on("SIGINT", () => { + Registry.getInstance().cleanup() + process.exit(0) +}) + +process.on("SIGTERM", () => { + Registry.getInstance().cleanup() + process.exit(0) +}) diff --git a/packages/socket/src/services/config.ts b/packages/socket/src/services/config.ts index 9c1bd50..e052542 100644 --- a/packages/socket/src/services/config.ts +++ b/packages/socket/src/services/config.ts @@ -2,11 +2,19 @@ import { QuizzWithId } from "@rahoot/common/types/game" import fs from "fs" import { resolve } from "path" -const getPath = (path: string) => resolve(process.cwd(), "../../config", path) +const getPath = (path: string = "") => + resolve(process.cwd(), "../../config", path) class Config { static init() { + const isConfigFolderExists = fs.existsSync(getPath()) + + if (!isConfigFolderExists) { + fs.mkdirSync(getPath()) + } + const isGameConfigExists = fs.existsSync(getPath("game.json")) + if (!isGameConfigExists) { fs.writeFileSync( getPath("game.json"), @@ -22,6 +30,7 @@ class Config { } const isQuizzExists = fs.existsSync(getPath("quizz")) + if (!isQuizzExists) { fs.mkdirSync(getPath("quizz")) @@ -65,20 +74,25 @@ class Config { static game() { const isExists = fs.existsSync(getPath("game.json")) + if (!isExists) { throw new Error("Game config not found") } try { const config = fs.readFileSync(getPath("game.json"), "utf-8") + return JSON.parse(config) } catch (error) { console.error("Failed to read game config:", error) } + + return {} } static quizz() { const isExists = fs.existsSync(getPath("quizz")) + if (!isExists) { return [] } @@ -99,9 +113,11 @@ class Config { ...config, } }) + return quizz || [] } catch (error) { console.error("Failed to read quizz config:", error) + return [] } } diff --git a/packages/socket/src/services/game.ts b/packages/socket/src/services/game.ts index 3e24428..cb8d3d5 100644 --- a/packages/socket/src/services/game.ts +++ b/packages/socket/src/services/game.ts @@ -1,6 +1,6 @@ import { Answer, Player, Quizz } from "@rahoot/common/types/game" import { Server, Socket } from "@rahoot/common/types/game/socket" -import { Status, StatusDataMap } from "@rahoot/common/types/game/status" +import { Status, STATUS, StatusDataMap } from "@rahoot/common/types/game/status" import { createInviteCode, timeToPoint } from "@rahoot/socket/utils/game" import sleep from "@rahoot/socket/utils/sleep" import { v4 as uuid } from "uuid" @@ -114,7 +114,7 @@ class Game { const playerData = { id: socket.id, clientId: socket.handshake.auth.clientId, - username: username, + username, points: 0, } @@ -148,7 +148,7 @@ class Game { } reconnect(socket: Socket) { - const clientId = socket.handshake.auth.clientId + const { clientId } = socket.handshake.auth const isManager = this.manager.clientId === clientId if (!isManager) { @@ -175,7 +175,7 @@ class Game { const status = this.managerStatus || this.lastBroadcastStatus || { - name: Status.WAIT, + name: STATUS.WAIT, data: { text: "Waiting for players" }, } @@ -188,7 +188,7 @@ class Game { console.log(`Manager reconnected to game ${this.inviteCode}`) - return + return true } const player = this.players.find((p) => p.clientId === clientId)! @@ -197,7 +197,7 @@ class Game { const status = this.playerStatus.get(oldSocketId) || this.lastBroadcastStatus || { - name: Status.WAIT, + name: STATUS.WAIT, data: { text: "Waiting for players" }, } @@ -223,9 +223,9 @@ class Game { return true } - async startCooldown(seconds: number) { + startCooldown(seconds: number): Promise { if (this.cooldown.active) { - return + return Promise.resolve() } this.cooldown.active = true @@ -237,6 +237,7 @@ class Game { this.cooldown.active = false clearInterval(cooldownTimeout) resolve() + return } @@ -246,10 +247,8 @@ class Game { }) } - async abortCooldown() { - if (this.cooldown.active) { - this.cooldown.active = false - } + abortCooldown() { + this.cooldown.active &&= false } async start(socket: Socket) { @@ -263,7 +262,7 @@ class Game { this.started = true - this.broadcastStatus(Status.SHOW_START, { + this.broadcastStatus(STATUS.SHOW_START, { time: 3, subject: this.quizz.subject, }) @@ -290,7 +289,7 @@ class Game { total: this.quizz.questions.length, }) - this.broadcastStatus(Status.SHOW_PREPARED, { + this.broadcastStatus(STATUS.SHOW_PREPARED, { totalAnswers: question.answers.length, questionNumber: this.round.currentQuestion + 1, }) @@ -301,7 +300,7 @@ class Game { return } - this.broadcastStatus(Status.SHOW_QUESTION, { + this.broadcastStatus(STATUS.SHOW_QUESTION, { question: question.question, image: question.image, cooldown: question.cooldown, @@ -315,7 +314,7 @@ class Game { this.round.startTime = Date.now() - this.broadcastStatus(Status.SELECT_ANSWER, { + this.broadcastStatus(STATUS.SELECT_ANSWER, { question: question.question, answers: question.answers, image: question.image, @@ -332,10 +331,11 @@ class Game { await this.showResults(question) } - async showResults(question: any) { + showResults(question: any) { const totalType = this.round.playersAnswers.reduce( (acc: Record, { answerId }) => { acc[answerId] = (acc[answerId] || 0) + 1 + return acc }, {} @@ -366,7 +366,7 @@ class Game { const rank = index + 1 const aheadPlayer = sortedPlayers[index - 1] - this.sendStatus(player.id, Status.SHOW_RESULT, { + this.sendStatus(player.id, STATUS.SHOW_RESULT, { correct: player.lastCorrect, message: player.lastCorrect ? "Nice!" : "Too bad", points: player.lastPoints, @@ -376,7 +376,7 @@ class Game { }) }) - this.sendStatus(this.manager.id, Status.SHOW_RESPONSES, { + this.sendStatus(this.manager.id, STATUS.SHOW_RESPONSES, { question: question.question, responses: totalType, correct: question.solution, @@ -387,7 +387,7 @@ class Game { this.round.playersAnswers = [] } - async selectAnswer(socket: Socket, answerId: number) { + selectAnswer(socket: Socket, answerId: number) { const player = this.players.find((player) => player.id === socket.id) const question = this.quizz.questions[this.round.currentQuestion] @@ -405,7 +405,7 @@ class Game { points: timeToPoint(this.round.startTime, question.time), }) - this.sendStatus(socket.id, Status.WAIT, { + this.sendStatus(socket.id, STATUS.WAIT, { text: "Waiting for the players to answer", }) @@ -458,7 +458,7 @@ class Game { if (isLastRound) { this.started = false - this.broadcastStatus(Status.FINISHED, { + this.broadcastStatus(STATUS.FINISHED, { subject: this.quizz.subject, top: sortedPlayers.slice(0, 3), }) @@ -466,7 +466,7 @@ class Game { return } - this.sendStatus(this.manager.id, Status.SHOW_LEADERBOARD, { + this.sendStatus(this.manager.id, STATUS.SHOW_LEADERBOARD, { leaderboard: sortedPlayers.slice(0, 5), }) } diff --git a/packages/socket/src/services/registry.ts b/packages/socket/src/services/registry.ts new file mode 100644 index 0000000..efe0fd5 --- /dev/null +++ b/packages/socket/src/services/registry.ts @@ -0,0 +1,158 @@ +import Game from "@rahoot/socket/services/game" +import dayjs from "dayjs" + +interface EmptyGame { + since: number + game: Game +} + +class Registry { + private static instance: Registry | null = null + private games: Game[] = [] + private emptyGames: EmptyGame[] = [] + private cleanupInterval: ReturnType | null = null + private readonly EMPTY_GAME_TIMEOUT_MINUTES = 5 + private readonly CLEANUP_INTERVAL_MS = 60_000 + + private constructor() { + this.startCleanupTask() + } + + static getInstance(): Registry { + Registry.instance ||= new Registry() + + return Registry.instance + } + + addGame(game: Game): void { + this.games.push(game) + console.log(`Game ${game.gameId} added. Total games: ${this.games.length}`) + } + + getGameById(gameId: string): Game | undefined { + return this.games.find((g) => g.gameId === gameId) + } + + getGameByInviteCode(inviteCode: string): Game | undefined { + return this.games.find((g) => g.inviteCode === inviteCode) + } + + getPlayerGame(clientId: string): Game | undefined { + return this.games.find((g) => + g.players.some((p) => p.clientId === clientId) + ) + } + + getManagerGame(clientId: string): Game | undefined { + return this.games.find((g) => g.manager.clientId === clientId) + } + + getGameByManagerSocketId(socketId: string): Game | undefined { + return this.games.find((g) => g.manager.id === socketId) + } + + getGameByPlayerSocketId(socketId: string): Game | undefined { + return this.games.find((g) => g.players.some((p) => p.id === socketId)) + } + + markGameAsEmpty(game: Game): void { + const alreadyEmpty = this.emptyGames.find( + (g) => g.game.gameId === game.gameId + ) + + if (!alreadyEmpty) { + this.emptyGames.push({ + since: dayjs().unix(), + game, + }) + console.log( + `Game ${game.gameId} marked as empty. Total empty games: ${this.emptyGames.length}` + ) + } + } + + reactivateGame(gameId: string): void { + const initialLength = this.emptyGames.length + this.emptyGames = this.emptyGames.filter((g) => g.game.gameId !== gameId) + + if (this.emptyGames.length < initialLength) { + console.log( + `Game ${gameId} reactivated. Remaining empty games: ${this.emptyGames.length}` + ) + } + } + + removeGame(gameId: string): boolean { + const initialLength = this.games.length + this.games = this.games.filter((g) => g.gameId !== gameId) + this.emptyGames = this.emptyGames.filter((g) => g.game.gameId !== gameId) + + const removed = this.games.length < initialLength + + if (removed) { + console.log(`Game ${gameId} removed. Total games: ${this.games.length}`) + } + + return removed + } + + getAllGames(): Game[] { + return [...this.games] + } + + getGameCount(): number { + return this.games.length + } + + getEmptyGameCount(): number { + return this.emptyGames.length + } + + private cleanupEmptyGames(): void { + const now = dayjs() + const stillEmpty = this.emptyGames.filter( + (g) => + now.diff(dayjs.unix(g.since), "minute") < + this.EMPTY_GAME_TIMEOUT_MINUTES + ) + + if (stillEmpty.length === this.emptyGames.length) { + return + } + + const removed = this.emptyGames.filter((g) => !stillEmpty.includes(g)) + const removedGameIds = removed.map((r) => r.game.gameId) + + this.games = this.games.filter((g) => !removedGameIds.includes(g.gameId)) + this.emptyGames = stillEmpty + + console.log( + `Removed ${removed.length} empty game(s). Remaining games: ${this.games.length}` + ) + } + + private startCleanupTask(): void { + this.cleanupInterval = setInterval(() => { + this.cleanupEmptyGames() + }, this.CLEANUP_INTERVAL_MS) + + console.log("Game cleanup task started") + } + + stopCleanupTask(): void { + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval) + this.cleanupInterval = null + console.log("Game cleanup task stopped") + } + } + + cleanup(): void { + this.stopCleanupTask() + this.games = [] + this.emptyGames = [] + console.log("Registry cleaned up") + } +} + +export default Registry diff --git a/packages/socket/src/utils/game.ts b/packages/socket/src/utils/game.ts index 9a7f92a..f6923d6 100644 --- a/packages/socket/src/utils/game.ts +++ b/packages/socket/src/utils/game.ts @@ -1,29 +1,28 @@ import { Socket } from "@rahoot/common/types/game/socket" import Game from "@rahoot/socket/services/game" +import Registry from "@rahoot/socket/services/registry" -export const withGame = ( +export const withGame = ( gameId: string | undefined, socket: Socket, - games: Game[], - handler: (game: Game) => T -): T | void => { - let game = null - - if (gameId) { - game = games.find((g) => g.gameId === gameId) - } else { - game = games.find( - (g) => - g.players.find((p) => p.id === socket.id) || g.manager.id === socket.id - ) - } - - if (!game) { + callback: (_game: Game) => void +): void => { + if (!gameId) { socket.emit("game:errorMessage", "Game not found") + return } - return handler(game) + const registry = Registry.getInstance() + const game = registry.getGameById(gameId) + + if (!game) { + socket.emit("game:errorMessage", "Game not found") + + return + } + + callback(game) } export const createInviteCode = (length = 6) => { @@ -50,25 +49,3 @@ export const timeToPoint = (startTime: number, secondes: number): number => { return points } - -export const findPlayerGameByClientId = (clientId: string, games: Game[]) => { - const playerGame = games.find((g) => - g.players.find((p) => p.clientId === clientId) - ) - - if (playerGame) { - return playerGame - } - - return null -} - -export const findManagerGameByClientId = (clientId: string, games: Game[]) => { - const managerGame = games.find((g) => g.manager.clientId === clientId) - - if (managerGame) { - return managerGame - } - - return null -} diff --git a/packages/socket/tsconfig.json b/packages/socket/tsconfig.json index 42ed9cb..43390f3 100644 --- a/packages/socket/tsconfig.json +++ b/packages/socket/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "target": "ESNext", "module": "ESNext", + "types": ["node"], "moduleResolution": "bundler", "strict": true, "skipLibCheck": true, diff --git a/packages/web/eslint.config.mjs b/packages/web/eslint.config.mjs index 8df583a..6b41f73 100644 --- a/packages/web/eslint.config.mjs +++ b/packages/web/eslint.config.mjs @@ -1,22 +1,23 @@ +import js from "@eslint/js" import nextPlugin from "@next/eslint-plugin-next" -import tsEslintPlugin from "@typescript-eslint/eslint-plugin" -import tsEslintParser from "@typescript-eslint/parser" import reactPlugin from "eslint-plugin-react" import reactHooksPlugin from "eslint-plugin-react-hooks" import { defineConfig } from "eslint/config" import globals from "globals" +import tseslint from "typescript-eslint" export default defineConfig([ { ignores: ["**/node_modules/**", "**/.next/**"], }, { - files: ["**/*.ts", "**/*.tsx"], + files: ["**/*.{ts,tsx}"], languageOptions: { - ecmaVersion: "latest", - sourceType: "module", - parser: tsEslintParser, + ...js.configs.recommended.languageOptions, + parser: tseslint.parser, parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, ecmaFeatures: { jsx: true }, }, globals: { @@ -25,7 +26,7 @@ export default defineConfig([ }, }, plugins: { - "@typescript-eslint": tsEslintPlugin, + "@typescript-eslint": tseslint.plugin, react: reactPlugin, "react-hooks": reactHooksPlugin, "@next/next": nextPlugin, @@ -36,6 +37,10 @@ export default defineConfig([ }, }, rules: { + ...js.configs.recommended.rules, + ...tseslint.configs.recommendedTypeChecked[0].rules, + ...reactPlugin.configs.recommended.rules, + "array-callback-return": [ "error", { allowImplicit: false, checkForEach: true, allowVoid: true }, @@ -213,7 +218,7 @@ export default defineConfig([ "space-before-blocks": "error", semi: ["error", "never"], - // Extra rules + // React + Hooks + Next.js "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "off", "react/no-unescaped-entities": ["error", { forbid: [">", "}"] }], diff --git a/packages/web/package.json b/packages/web/package.json index 7fa8e4c..7d547d7 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -12,7 +12,7 @@ "@rahoot/socket": "workspace:*", "@t3-oss/env-nextjs": "^0.13.8", "clsx": "^2.1.1", - "ky": "^1.11.0", + "ky": "^1.12.0", "next": "15.5.4", "react": "19.1.0", "react-confetti": "^6.4.0", @@ -26,21 +26,20 @@ "zustand": "^5.0.8" }, "devDependencies": { - "@eslint/eslintrc": "^3", - "@tailwindcss/postcss": "^4", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "@typescript-eslint/eslint-plugin": "^8.45.0", - "@typescript-eslint/parser": "^8.45.0", - "eslint": "^9", + "@eslint/eslintrc": "^3.3.1", + "@tailwindcss/postcss": "^4.1.14", + "@types/node": "^20.19.22", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "eslint": "^9.38.0", "eslint-config-next": "15.5.4", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^6.1.1", "globals": "^16.4.0", "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", - "tailwindcss": "^4", - "typescript": "^5" + "tailwindcss": "^4.1.14", + "typescript": "^5.9.3", + "typescript-eslint": "^8.46.1" } } diff --git a/packages/web/src/app/(auth)/layout.tsx b/packages/web/src/app/(auth)/layout.tsx index 379d167..cda8be6 100644 --- a/packages/web/src/app/(auth)/layout.tsx +++ b/packages/web/src/app/(auth)/layout.tsx @@ -1,6 +1,9 @@ "use client" +import logo from "@rahoot/web/assets/logo.svg" +import Loader from "@rahoot/web/components/Loader" import { useSocket } from "@rahoot/web/contexts/socketProvider" +import Image from "next/image" import { PropsWithChildren, useEffect } from "react" const AuthLayout = ({ children }: PropsWithChildren) => { @@ -11,7 +14,34 @@ const AuthLayout = ({ children }: PropsWithChildren) => { } }, [connect, isConnected]) - return children + if (!isConnected) { + return ( +
+
+
+
+
+ + logo + +

+ Loading... +

+
+ ) + } + + return ( +
+
+
+
+
+ + logo + {children} +
+ ) } export default AuthLayout diff --git a/packages/web/src/app/(auth)/manager/page.tsx b/packages/web/src/app/(auth)/manager/page.tsx index 63b63a0..b7c3b05 100644 --- a/packages/web/src/app/(auth)/manager/page.tsx +++ b/packages/web/src/app/(auth)/manager/page.tsx @@ -31,7 +31,6 @@ export default function Manager() { socket?.emit("manager:auth", password) } const handleCreate = (quizzId: string) => { - console.log(quizzId) socket?.emit("game:create", quizzId) } diff --git a/packages/web/src/app/(auth)/page.tsx b/packages/web/src/app/(auth)/page.tsx index b4dff88..546b3b8 100644 --- a/packages/web/src/app/(auth)/page.tsx +++ b/packages/web/src/app/(auth)/page.tsx @@ -1,11 +1,9 @@ "use client" -import logo from "@rahoot/web/assets/logo.svg" import Room from "@rahoot/web/components/game/join/Room" import Username from "@rahoot/web/components/game/join/Username" import { useEvent, useSocket } from "@rahoot/web/contexts/socketProvider" import { usePlayerStore } from "@rahoot/web/stores/player" -import Image from "next/image" import { useEffect } from "react" import toast from "react-hot-toast" @@ -23,16 +21,9 @@ export default function Home() { toast.error(message) }) - return ( -
-
-
-
-
+ if (player) { + return + } - logo - - {!player ? : } -
- ) + return } diff --git a/packages/web/src/app/game/[gameId]/page.tsx b/packages/web/src/app/game/[gameId]/page.tsx index 6bcd0b9..1ab789a 100644 --- a/packages/web/src/app/game/[gameId]/page.tsx +++ b/packages/web/src/app/game/[gameId]/page.tsx @@ -1,6 +1,6 @@ "use client" -import { Status } from "@rahoot/common/types/game/status" +import { STATUS } from "@rahoot/common/types/game/status" import GameWrapper from "@rahoot/web/components/game/GameWrapper" import Answers from "@rahoot/web/components/game/states/Answers" import Prepared from "@rahoot/web/components/game/states/Prepared" @@ -17,10 +17,9 @@ import toast from "react-hot-toast" export default function Game() { const router = useRouter() - const { socket, isConnected } = useSocket() + const { socket } = useSocket() const { gameId: gameIdParam }: { gameId?: string } = useParams() - const { status, setPlayer, logout, setGameId, setStatus, resetStatus } = - usePlayerStore() + const { status, setPlayer, setGameId, setStatus, reset } = usePlayerStore() const { setQuestionStates } = useQuestionStore() useEvent("connect", () => { @@ -45,16 +44,16 @@ export default function Game() { } }) - useEvent("game:reset", () => { + useEvent("game:kick", () => { router.replace("/") - resetStatus() - logout() - toast("The game has been reset by the host") + reset() }) - if (!isConnected) { - return null - } + useEvent("game:reset", () => { + router.replace("/") + reset() + toast("The game has been reset by the host") + }) if (!gameIdParam) { return null @@ -63,36 +62,36 @@ export default function Game() { let component = null switch (status.name) { - case Status.WAIT: + case STATUS.WAIT: component = break - case Status.SHOW_START: + case STATUS.SHOW_START: component = break - case Status.SHOW_PREPARED: + case STATUS.SHOW_PREPARED: component = break - case Status.SHOW_QUESTION: + case STATUS.SHOW_QUESTION: component = break - case Status.SHOW_RESULT: + case STATUS.SHOW_RESULT: component = break - case Status.SELECT_ANSWER: + case STATUS.SELECT_ANSWER: component = break } - return {component} + return {component} } diff --git a/packages/web/src/app/game/manager/[gameId]/page.tsx b/packages/web/src/app/game/manager/[gameId]/page.tsx index 67cf094..b00d291 100644 --- a/packages/web/src/app/game/manager/[gameId]/page.tsx +++ b/packages/web/src/app/game/manager/[gameId]/page.tsx @@ -1,6 +1,6 @@ "use client" -import { Status } from "@rahoot/common/types/game/status" +import { STATUS } from "@rahoot/common/types/game/status" import GameWrapper from "@rahoot/web/components/game/GameWrapper" import Answers from "@rahoot/web/components/game/states/Answers" import Leaderboard from "@rahoot/web/components/game/states/Leaderboard" @@ -15,15 +15,14 @@ import { useManagerStore } from "@rahoot/web/stores/manager" import { useQuestionStore } from "@rahoot/web/stores/question" import { GAME_STATE_COMPONENTS_MANAGER } from "@rahoot/web/utils/constants" import { useParams, useRouter } from "next/navigation" -import { useEffect, useState } from "react" import toast from "react-hot-toast" export default function ManagerGame() { const router = useRouter() const { gameId: gameIdParam }: { gameId?: string } = useParams() - const { socket, isConnected } = useSocket() - const [nextText, setNextText] = useState("Start") - const { gameId, status, setGameId, setStatus, setPlayers } = useManagerStore() + const { socket } = useSocket() + const { gameId, status, setGameId, setStatus, setPlayers, reset } = + useManagerStore() const { setQuestionStates } = useQuestionStore() useEvent("game:status", ({ name, data }) => { @@ -41,6 +40,7 @@ export default function ManagerGame() { useEvent( "manager:successReconnect", ({ gameId, status, players, currentQuestion }) => { + console.log("manager:successReconnect", gameId) setGameId(gameId) setStatus(status.name, status.data) setPlayers(players) @@ -50,43 +50,32 @@ export default function ManagerGame() { useEvent("game:reset", () => { router.replace("/manager") + reset() toast("Game is not available anymore") }) - useEffect(() => { - if (status.name === Status.SHOW_START) { - setNextText("Start") - } - }, [status.name]) - - if (!isConnected) { - return null - } - - if (!gameId) { - return null - } - const handleSkip = () => { - setNextText("Skip") + if (!gameId) { + return + } switch (status.name) { - case Status.SHOW_ROOM: + case STATUS.SHOW_ROOM: socket?.emit("manager:startGame", { gameId }) break - case Status.SELECT_ANSWER: + case STATUS.SELECT_ANSWER: socket?.emit("manager:abortQuiz", { gameId }) break - case Status.SHOW_RESPONSES: + case STATUS.SHOW_RESPONSES: socket?.emit("manager:showLeaderboard", { gameId }) break - case Status.SHOW_LEADERBOARD: + case STATUS.SHOW_LEADERBOARD: socket?.emit("manager:nextQuestion", { gameId }) break @@ -96,49 +85,49 @@ export default function ManagerGame() { let component = null switch (status.name) { - case Status.SHOW_ROOM: + case STATUS.SHOW_ROOM: component = break - case Status.SHOW_START: + case STATUS.SHOW_START: component = break - case Status.SHOW_PREPARED: + case STATUS.SHOW_PREPARED: component = break - case Status.SHOW_QUESTION: + case STATUS.SHOW_QUESTION: component = break - case Status.SELECT_ANSWER: + case STATUS.SELECT_ANSWER: component = break - case Status.SHOW_RESPONSES: + case STATUS.SHOW_RESPONSES: component = break - case Status.SHOW_LEADERBOARD: + case STATUS.SHOW_LEADERBOARD: component = break - case Status.FINISHED: + case STATUS.FINISHED: component = break } return ( - + {component} ) diff --git a/packages/web/src/app/globals.css b/packages/web/src/app/globals.css index c7b2bcf..ede069a 100644 --- a/packages/web/src/app/globals.css +++ b/packages/web/src/app/globals.css @@ -5,6 +5,11 @@ --color-secondary: #1a140b; } +button:not(:disabled), +[role="button"]:not(:disabled) { + cursor: pointer; +} + .btn-shadow { box-shadow: rgba(0, 0, 0, 0.25) 0px -4px inset; } diff --git a/packages/web/src/components/Loader.tsx b/packages/web/src/components/Loader.tsx index 0a403c3..a760dec 100644 --- a/packages/web/src/components/Loader.tsx +++ b/packages/web/src/components/Loader.tsx @@ -1,6 +1,12 @@ import loader from "@rahoot/web/assets/loader.svg" import Image from "next/image" -export default function Loader() { - return loader +type Props = { + className?: string } + +const Loader = ({ className }: Props) => ( + loader +) + +export default Loader diff --git a/packages/web/src/components/game/GameWrapper.tsx b/packages/web/src/components/game/GameWrapper.tsx index 225b9ea..49eb8ed 100644 --- a/packages/web/src/components/game/GameWrapper.tsx +++ b/packages/web/src/components/game/GameWrapper.tsx @@ -1,41 +1,32 @@ "use client" +import { Status } from "@rahoot/common/types/game/status" import background from "@rahoot/web/assets/background.webp" import Button from "@rahoot/web/components/Button" import { useEvent, useSocket } from "@rahoot/web/contexts/socketProvider" import { usePlayerStore } from "@rahoot/web/stores/player" import { useQuestionStore } from "@rahoot/web/stores/question" +import { MANAGER_SKIP_BTN } from "@rahoot/web/utils/constants" import Image from "next/image" -import { useRouter } from "next/navigation" -import { PropsWithChildren, useEffect } from "react" +import { PropsWithChildren } from "react" +import Loader from "../Loader" type Props = PropsWithChildren & { - textNext?: string + statusName: Status onNext?: () => void manager?: boolean } export default function GameWrapper({ children, - textNext, + statusName, onNext, manager, }: Props) { - const { isConnected, connect } = useSocket() - const { player, logout } = usePlayerStore() + const { isConnected } = useSocket() + const { player } = usePlayerStore() const { questionStates, setQuestionStates } = useQuestionStore() - const router = useRouter() - - useEffect(() => { - if (!isConnected) { - connect() - } - }, [connect, isConnected]) - - useEvent("game:kick", () => { - logout() - router.replace("/") - }) + const next = MANAGER_SKIP_BTN[statusName] || null useEvent("game:updateQuestion", ({ current, total }) => { setQuestionStates({ @@ -54,32 +45,41 @@ export default function GameWrapper({ /> -
- {questionStates && ( -
- {`${questionStates.current} / ${questionStates.total}`} -
- )} - - {manager && ( - - )} -
- - {children} - - {!manager && ( -
-

{player?.username}

-
- {player?.points} -
+ {!isConnected && !statusName ? ( +
+ +

Connecting...

+ ) : ( + <> +
+ {questionStates && ( +
+ {`${questionStates.current} / ${questionStates.total}`} +
+ )} + + {manager && next && ( + + )} +
+ + {children} + + {!manager && ( +
+

{player?.username}

+
+ {player?.points} +
+
+ )} + )} ) diff --git a/packages/web/src/components/game/create/ManagerPassword.tsx b/packages/web/src/components/game/create/ManagerPassword.tsx index 524f06a..653e8e5 100644 --- a/packages/web/src/components/game/create/ManagerPassword.tsx +++ b/packages/web/src/components/game/create/ManagerPassword.tsx @@ -1,15 +1,12 @@ -import logo from "@rahoot/web/assets/logo.svg" import Button from "@rahoot/web/components/Button" import Form from "@rahoot/web/components/Form" import Input from "@rahoot/web/components/Input" import { useEvent } from "@rahoot/web/contexts/socketProvider" -import Image from "next/image" import { KeyboardEvent, useState } from "react" import toast from "react-hot-toast" type Props = { - // eslint-disable-next-line no-unused-vars - onSubmit: (password: string) => void + onSubmit: (_password: string) => void } export default function ManagerPassword({ onSubmit }: Props) { @@ -30,23 +27,14 @@ export default function ManagerPassword({ onSubmit }: Props) { }) return ( -
-
-
-
-
- - logo - -
- setPassword(e.target.value)} - onKeyDown={handleKeyDown} - placeholder="Manager password" - /> - -
-
+
+ setPassword(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Manager password" + /> + +
) } diff --git a/packages/web/src/components/game/create/SelectQuizz.tsx b/packages/web/src/components/game/create/SelectQuizz.tsx index d4b0d5b..2fc6b81 100644 --- a/packages/web/src/components/game/create/SelectQuizz.tsx +++ b/packages/web/src/components/game/create/SelectQuizz.tsx @@ -1,8 +1,6 @@ import { Quizz } from "@rahoot/common/types/game" -import logo from "@rahoot/web/assets/logo.svg" import Button from "@rahoot/web/components/Button" import clsx from "clsx" -import Image from "next/image" import { useState } from "react" import toast from "react-hot-toast" @@ -10,8 +8,7 @@ type QuizzWithId = Quizz & { id: string } type Props = { quizzList: QuizzWithId[] - // eslint-disable-next-line no-unused-vars - onSelect: (id: string) => void + onSelect: (_id: string) => void } export default function SelectQuizz({ quizzList, onSelect }: Props) { @@ -36,44 +33,32 @@ export default function SelectQuizz({ quizzList, onSelect }: Props) { } return ( -
-
-
-
-
+
+
+

Select a quizz

+
+ {quizzList.map((quizz) => ( + - ))} -
+ >
+ + ))}
-
- + + ) } diff --git a/packages/web/src/components/icons/Pentagon.tsx b/packages/web/src/components/icons/Pentagon.tsx index d734871..63e9cef 100644 --- a/packages/web/src/components/icons/Pentagon.tsx +++ b/packages/web/src/components/icons/Pentagon.tsx @@ -17,9 +17,9 @@ export default function Pentagon({ className, fill, stroke }: Props) { viewBox="-40.96 -40.96 593.93 593.93" transform="rotate(180)" stroke={fill} - stroke-width="0.005120100000000001" + strokeWidth="0.005120100000000001" > - + = { status: Status players: Player[] - setGameId: (gameId: string | null) => void - - setStatus: (name: K, data: T[K]) => void + setGameId: (_gameId: string | null) => void + setStatus: (_name: K, _data: T[K]) => void resetStatus: () => void + setPlayers: (_players: Player[]) => void - setPlayers: (players: Player[]) => void + reset: () => void } const initialStatus = createStatus("SHOW_ROOM", { text: "Waiting for the players", }) -export const useManagerStore = create>((set) => ({ +const initialState = { gameId: null, status: initialStatus, players: [], +} + +export const useManagerStore = create>((set) => ({ + ...initialState, setGameId: (gameId) => set({ gameId }), @@ -32,4 +35,6 @@ export const useManagerStore = create>((set) => ({ resetStatus: () => set({ status: initialStatus }), setPlayers: (players) => set({ players }), + + reset: () => set(initialState), })) diff --git a/packages/web/src/stores/player.tsx b/packages/web/src/stores/player.tsx index 2517947..a05b1e4 100644 --- a/packages/web/src/stores/player.tsx +++ b/packages/web/src/stores/player.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ import { StatusDataMap } from "@rahoot/common/types/game/status" import { createStatus, Status } from "@rahoot/web/utils/createStatus" import { create } from "zustand" @@ -13,27 +12,30 @@ type PlayerStore = { player: PlayerState | null status: Status - setGameId: (gameId: string | null) => void + setGameId: (_gameId: string | null) => void - setPlayer: (state: PlayerState) => void - login: (gameId: string) => void - join: (username: string) => void - updatePoints: (points: number) => void - logout: () => void + setPlayer: (_state: PlayerState) => void + login: (_gameId: string) => void + join: (_username: string) => void + updatePoints: (_points: number) => void - setStatus: (name: K, data: T[K]) => void - resetStatus: () => void + setStatus: (_name: K, _data: T[K]) => void + + reset: () => void } const initialStatus = createStatus("WAIT", { text: "Waiting for the players", }) -export const usePlayerStore = create>((set) => ({ +const initialState = { gameId: null, player: null, status: initialStatus, - currentQuestion: null, +} + +export const usePlayerStore = create>((set) => ({ + ...initialState, setGameId: (gameId) => set({ gameId }), @@ -55,8 +57,7 @@ export const usePlayerStore = create>((set) => ({ player: { ...state.player, points }, })), - logout: () => set({ player: null }), - setStatus: (name, data) => set({ status: createStatus(name, data) }), - resetStatus: () => set({ status: initialStatus }), + + reset: () => set(initialState), })) diff --git a/packages/web/src/stores/question.tsx b/packages/web/src/stores/question.tsx index 6180ee3..033ba9b 100644 --- a/packages/web/src/stores/question.tsx +++ b/packages/web/src/stores/question.tsx @@ -1,10 +1,9 @@ -/* eslint-disable no-unused-vars */ import { GameUpdateQuestion } from "@rahoot/common/types/game" import { create } from "zustand" type QuestionStore = { questionStates: GameUpdateQuestion | null - setQuestionStates: (state: GameUpdateQuestion) => void + setQuestionStates: (_state: GameUpdateQuestion) => void } export const useQuestionStore = create((set) => ({ diff --git a/packages/web/src/utils/constants.ts b/packages/web/src/utils/constants.ts index ee5c04f..822b485 100644 --- a/packages/web/src/utils/constants.ts +++ b/packages/web/src/utils/constants.ts @@ -9,7 +9,7 @@ import Room from "@rahoot/web/components/game/states/Room" import Start from "@rahoot/web/components/game/states/Start" import Wait from "@rahoot/web/components/game/states/Wait" -import { Status } from "@rahoot/common/types/game/status" +import { STATUS } from "@rahoot/common/types/game/status" import Circle from "@rahoot/web/components/icons/Circle" import Rhombus from "@rahoot/web/components/icons/Rhombus" import Square from "@rahoot/web/components/icons/Square" @@ -26,7 +26,7 @@ export const ANSWERS_ICONS = [Triangle, Rhombus, Circle, Square] export const GAME_STATES = { status: { - name: Status.WAIT, + name: STATUS.WAIT, data: { text: "Waiting for the players" }, }, question: { @@ -36,20 +36,20 @@ export const GAME_STATES = { } export const GAME_STATE_COMPONENTS = { - [Status.SELECT_ANSWER]: Answers, - [Status.SHOW_QUESTION]: Question, - [Status.WAIT]: Wait, - [Status.SHOW_START]: Start, - [Status.SHOW_RESULT]: Result, - [Status.SHOW_PREPARED]: Prepared, + [STATUS.SELECT_ANSWER]: Answers, + [STATUS.SHOW_QUESTION]: Question, + [STATUS.WAIT]: Wait, + [STATUS.SHOW_START]: Start, + [STATUS.SHOW_RESULT]: Result, + [STATUS.SHOW_PREPARED]: Prepared, } export const GAME_STATE_COMPONENTS_MANAGER = { ...GAME_STATE_COMPONENTS, - [Status.SHOW_ROOM]: Room, - [Status.SHOW_RESPONSES]: Responses, - [Status.SHOW_LEADERBOARD]: Leaderboard, - [Status.FINISHED]: Podium, + [STATUS.SHOW_ROOM]: Room, + [STATUS.SHOW_RESPONSES]: Responses, + [STATUS.SHOW_LEADERBOARD]: Leaderboard, + [STATUS.FINISHED]: Podium, } export const SFX_ANSWERS_MUSIC = "/sounds/answersMusic.mp3" @@ -61,3 +61,16 @@ export const SFX_PODIUM_THREE = "/sounds/three.mp3" export const SFX_PODIUM_SECOND = "/sounds/second.mp3" export const SFX_PODIUM_FIRST = "/sounds/first.mp3" export const SFX_SNEAR_ROOL = "/sounds/snearRoll.mp3" + +export const MANAGER_SKIP_BTN = { + [STATUS.SHOW_ROOM]: "Start Game", + [STATUS.SHOW_START]: null, + [STATUS.SHOW_PREPARED]: null, + [STATUS.SHOW_QUESTION]: null, + [STATUS.SELECT_ANSWER]: "Skip", + [STATUS.SHOW_RESULT]: null, + [STATUS.SHOW_RESPONSES]: "Next", + [STATUS.SHOW_LEADERBOARD]: "Next", + [STATUS.FINISHED]: null, + [STATUS.WAIT]: null, +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4cb3b04..51b12c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,15 +21,27 @@ importers: specifier: ^4.8.1 version: 4.8.1 zod: - specifier: ^3.22.4 + specifier: ^3.25.76 version: 3.25.76 devDependencies: + '@eslint/js': + specifier: ^9.38.0 + version: 9.38.0 '@types/node': - specifier: ^20.11.0 - version: 20.19.17 + specifier: ^20.19.22 + version: 20.19.22 + eslint: + specifier: ^9.38.0 + version: 9.38.0(jiti@2.6.1) + globals: + specifier: ^16.4.0 + version: 16.4.0 typescript: - specifier: ^5.3.3 - version: 5.9.2 + specifier: ^5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.46.1 + version: 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) packages/socket: dependencies: @@ -39,6 +51,9 @@ importers: '@t3-oss/env-core': specifier: ^0.13.8 version: 0.13.8(typescript@5.9.3)(zod@4.1.11) + dayjs: + specifier: ^1.11.18 + version: 1.11.18 socket.io: specifier: ^4.8.1 version: 4.8.1 @@ -49,12 +64,27 @@ importers: specifier: ^4.1.11 version: 4.1.11 devDependencies: + '@eslint/js': + specifier: ^9.38.0 + version: 9.38.0 + '@types/node': + specifier: ^24.8.1 + version: 24.8.1 esbuild: specifier: ^0.25.10 version: 0.25.10 + eslint: + specifier: ^9 + version: 9.36.0(jiti@2.6.1) + globals: + specifier: ^16.4.0 + version: 16.4.0 tsx: specifier: ^4.20.6 version: 4.20.6 + typescript-eslint: + specifier: ^8.46.1 + version: 8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) packages/web: dependencies: @@ -66,13 +96,13 @@ importers: version: link:../socket '@t3-oss/env-nextjs': specifier: ^0.13.8 - version: 0.13.8(typescript@5.9.2)(zod@4.1.12) + version: 0.13.8(typescript@5.9.3)(zod@4.1.12) clsx: specifier: ^2.1.1 version: 2.1.1 ky: - specifier: ^1.11.0 - version: 1.11.0 + specifier: ^1.12.0 + version: 1.12.0 next: specifier: 15.5.4 version: 15.5.4(@babel/core@7.28.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -105,41 +135,35 @@ importers: version: 4.1.12 zustand: specifier: ^5.0.8 - version: 5.0.8(@types/react@19.1.15)(react@19.1.0) + version: 5.0.8(@types/react@19.2.2)(react@19.1.0) devDependencies: '@eslint/eslintrc': - specifier: ^3 + specifier: ^3.3.1 version: 3.3.1 '@tailwindcss/postcss': - specifier: ^4 - version: 4.1.13 + specifier: ^4.1.14 + version: 4.1.14 '@types/node': - specifier: ^20 - version: 20.19.17 + specifier: ^20.19.22 + version: 20.19.22 '@types/react': - specifier: ^19 - version: 19.1.15 + specifier: ^19.2.2 + version: 19.2.2 '@types/react-dom': - specifier: ^19 - version: 19.1.9(@types/react@19.1.15) - '@typescript-eslint/eslint-plugin': - specifier: ^8.45.0 - version: 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/parser': - specifier: ^8.45.0 - version: 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + specifier: ^19.2.2 + version: 19.2.2(@types/react@19.2.2) eslint: - specifier: ^9 - version: 9.36.0(jiti@2.6.0) + specifier: ^9.38.0 + version: 9.38.0(jiti@2.6.1) eslint-config-next: specifier: 15.5.4 - version: 15.5.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + version: 15.5.4(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react: specifier: ^7.37.5 - version: 7.37.5(eslint@9.36.0(jiti@2.6.0)) + version: 7.37.5(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-react-hooks: specifier: ^6.1.1 - version: 6.1.1(eslint@9.36.0(jiti@2.6.0)) + version: 6.1.1(eslint@9.38.0(jiti@2.6.1)) globals: specifier: ^16.4.0 version: 16.4.0 @@ -150,11 +174,14 @@ importers: specifier: ^0.6.14 version: 0.6.14(prettier@3.6.2) tailwindcss: - specifier: ^4 - version: 4.1.13 + specifier: ^4.1.14 + version: 4.1.14 typescript: - specifier: ^5 - version: 5.9.2 + specifier: ^5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.46.1 + version: 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) packages: @@ -408,14 +435,26 @@ packages: resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.3.1': resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.4.1': + resolution: {integrity: sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.15.2': resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.16.0': + resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -424,14 +463,26 @@ packages: resolution: {integrity: sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.38.0': + resolution: {integrity: sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.3.5': resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.4.0': + resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -670,8 +721,8 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@rushstack/eslint-patch@1.12.0': - resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==} + '@rushstack/eslint-patch@1.14.0': + resolution: {integrity: sha512-WJFej426qe4RWOm9MMtP4V3CV4AucXolQty+GRgAWLgQXmpCuwzs7hEpxxhSc/znXUSxum9d/P/32MW0FlAAlA==} '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -713,65 +764,65 @@ packages: zod: optional: true - '@tailwindcss/node@4.1.13': - resolution: {integrity: sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==} + '@tailwindcss/node@4.1.14': + resolution: {integrity: sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==} - '@tailwindcss/oxide-android-arm64@4.1.13': - resolution: {integrity: sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==} + '@tailwindcss/oxide-android-arm64@4.1.14': + resolution: {integrity: sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.13': - resolution: {integrity: sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==} + '@tailwindcss/oxide-darwin-arm64@4.1.14': + resolution: {integrity: sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.13': - resolution: {integrity: sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==} + '@tailwindcss/oxide-darwin-x64@4.1.14': + resolution: {integrity: sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.13': - resolution: {integrity: sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==} + '@tailwindcss/oxide-freebsd-x64@4.1.14': + resolution: {integrity: sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': - resolution: {integrity: sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': + resolution: {integrity: sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': - resolution: {integrity: sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==} + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': + resolution: {integrity: sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.13': - resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==} + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': + resolution: {integrity: sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.13': - resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==} + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': + resolution: {integrity: sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.13': - resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==} + '@tailwindcss/oxide-linux-x64-musl@4.1.14': + resolution: {integrity: sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.13': - resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==} + '@tailwindcss/oxide-wasm32-wasi@4.1.14': + resolution: {integrity: sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -782,24 +833,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': - resolution: {integrity: sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==} + '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': + resolution: {integrity: sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.13': - resolution: {integrity: sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==} + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': + resolution: {integrity: sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.13': - resolution: {integrity: sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==} + '@tailwindcss/oxide@4.1.14': + resolution: {integrity: sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==} engines: {node: '>= 10'} - '@tailwindcss/postcss@4.1.13': - resolution: {integrity: sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==} + '@tailwindcss/postcss@4.1.14': + resolution: {integrity: sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg==} '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -816,74 +867,77 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@20.19.17': - resolution: {integrity: sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==} + '@types/node@20.19.22': + resolution: {integrity: sha512-hRnu+5qggKDSyWHlnmThnUqg62l29Aj/6vcYgUaSFL9oc7DVjeWEQN3PRgdSc6F8d9QRMWkf36CLMch1Do/+RQ==} - '@types/react-dom@19.1.9': - resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} + '@types/node@24.8.1': + resolution: {integrity: sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==} + + '@types/react-dom@19.2.2': + resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} peerDependencies: - '@types/react': ^19.0.0 + '@types/react': ^19.2.0 - '@types/react@19.1.15': - resolution: {integrity: sha512-+kLxJpaJzXybyDyFXYADyP1cznTO8HSuBpenGlnKOAkH4hyNINiywvXS/tGJhsrGGP/gM185RA3xpjY0Yg4erA==} + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} - '@typescript-eslint/eslint-plugin@8.45.0': - resolution: {integrity: sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==} + '@typescript-eslint/eslint-plugin@8.46.1': + resolution: {integrity: sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.45.0 + '@typescript-eslint/parser': ^8.46.1 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.45.0': - resolution: {integrity: sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==} + '@typescript-eslint/parser@8.46.1': + resolution: {integrity: sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.45.0': - resolution: {integrity: sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==} + '@typescript-eslint/project-service@8.46.1': + resolution: {integrity: sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.45.0': - resolution: {integrity: sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==} + '@typescript-eslint/scope-manager@8.46.1': + resolution: {integrity: sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.45.0': - resolution: {integrity: sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==} + '@typescript-eslint/tsconfig-utils@8.46.1': + resolution: {integrity: sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.45.0': - resolution: {integrity: sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==} + '@typescript-eslint/type-utils@8.46.1': + resolution: {integrity: sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.45.0': - resolution: {integrity: sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==} + '@typescript-eslint/types@8.46.1': + resolution: {integrity: sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.45.0': - resolution: {integrity: sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==} + '@typescript-eslint/typescript-estree@8.46.1': + resolution: {integrity: sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.45.0': - resolution: {integrity: sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==} + '@typescript-eslint/utils@8.46.1': + resolution: {integrity: sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.45.0': - resolution: {integrity: sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==} + '@typescript-eslint/visitor-keys@8.46.1': + resolution: {integrity: sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -1052,8 +1106,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.10.3: - resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} + axe-core@4.11.0: + resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} engines: {node: '>=4'} axobject-query@4.1.0: @@ -1067,8 +1121,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.8.12: - resolution: {integrity: sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==} + baseline-browser-mapping@2.8.18: + resolution: {integrity: sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==} hasBin: true brace-expansion@1.1.12: @@ -1102,11 +1156,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001745: - resolution: {integrity: sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==} - - caniuse-lite@1.0.30001747: - resolution: {integrity: sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==} + caniuse-lite@1.0.30001751: + resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1166,6 +1217,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + dayjs@1.11.18: + resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -1203,8 +1257,8 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - detect-libc@2.1.1: - resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} doctrine@2.1.0: @@ -1231,8 +1285,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.230: - resolution: {integrity: sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==} + electron-to-chromium@1.5.237: + resolution: {integrity: sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1399,6 +1453,16 @@ packages: jiti: optional: true + eslint@9.38.0: + resolution: {integrity: sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + espree@10.4.0: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1486,6 +1550,10 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1505,6 +1573,9 @@ packages: get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + get-tsconfig@4.12.0: + resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1525,8 +1596,8 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - goober@2.1.16: - resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==} + goober@2.1.18: + resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} peerDependencies: csstype: ^3.0.10 @@ -1633,8 +1704,8 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} is-glob@4.0.3: @@ -1703,8 +1774,8 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} - jiti@2.6.0: - resolution: {integrity: sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true js-tokens@4.0.0: @@ -1744,8 +1815,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - ky@1.11.0: - resolution: {integrity: sha512-NEyo0ICpS0cqSuyoJFMCnHOZJILqXsKhIZlHJGDYaH8OB5IFrGzuBpEwyoMZG6gUKMPrazH30Ax5XKaujvD8ag==} + ky@1.12.0: + resolution: {integrity: sha512-YRLmSUHCwOJRBMArtqMRLOmO7fewn3yOoui6aB8ERkRVXupa0UiaQaKbIXteMt4jUElhbdqTMsLFHs8APxxUoQ==} engines: {node: '>=18'} language-subtag-registry@0.3.23: @@ -1886,8 +1957,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-postinstall@0.3.3: - resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==} + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} hasBin: true @@ -1919,8 +1990,8 @@ packages: sass: optional: true - node-releases@2.0.23: - resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} + node-releases@2.0.25: + resolution: {integrity: sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -2171,6 +2242,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2289,11 +2365,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tailwindcss@4.1.13: - resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} + tailwindcss@4.1.14: + resolution: {integrity: sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==} - tapable@2.2.3: - resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} tar@7.5.1: @@ -2358,10 +2434,12 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} - engines: {node: '>=14.17'} - hasBin: true + typescript-eslint@8.46.1: + resolution: {integrity: sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} @@ -2375,6 +2453,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.14.0: + resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -2686,9 +2767,14 @@ snapshots: '@esbuild/win32-x64@0.25.10': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@2.6.0))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@2.6.1))': dependencies: - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.36.0(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.9.0(eslint@9.38.0(jiti@2.6.1))': + dependencies: + eslint: 9.38.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -2701,12 +2787,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + '@eslint/config-helpers@0.3.1': {} + '@eslint/config-helpers@0.4.1': + dependencies: + '@eslint/core': 0.16.0 + '@eslint/core@0.15.2': dependencies: '@types/json-schema': 7.0.15 + '@eslint/core@0.16.0': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 @@ -2723,13 +2825,22 @@ snapshots: '@eslint/js@9.36.0': {} + '@eslint/js@9.38.0': {} + '@eslint/object-schema@2.1.6': {} + '@eslint/object-schema@2.1.7': {} + '@eslint/plugin-kit@0.3.5': dependencies: '@eslint/core': 0.15.2 levn: 0.4.1 + '@eslint/plugin-kit@0.4.0': + dependencies: + '@eslint/core': 0.16.0 + levn: 0.4.1 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -2906,7 +3017,7 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@rushstack/eslint-patch@1.12.0': {} + '@rushstack/eslint-patch@1.14.0': {} '@socket.io/component-emitter@3.1.2': {} @@ -2914,94 +3025,94 @@ snapshots: dependencies: tslib: 2.8.1 - '@t3-oss/env-core@0.13.8(typescript@5.9.2)(zod@4.1.12)': - optionalDependencies: - typescript: 5.9.2 - zod: 4.1.12 - '@t3-oss/env-core@0.13.8(typescript@5.9.3)(zod@4.1.11)': optionalDependencies: typescript: 5.9.3 zod: 4.1.11 - '@t3-oss/env-nextjs@0.13.8(typescript@5.9.2)(zod@4.1.12)': - dependencies: - '@t3-oss/env-core': 0.13.8(typescript@5.9.2)(zod@4.1.12) + '@t3-oss/env-core@0.13.8(typescript@5.9.3)(zod@4.1.12)': optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 zod: 4.1.12 - '@tailwindcss/node@4.1.13': + '@t3-oss/env-nextjs@0.13.8(typescript@5.9.3)(zod@4.1.12)': + dependencies: + '@t3-oss/env-core': 0.13.8(typescript@5.9.3)(zod@4.1.12) + optionalDependencies: + typescript: 5.9.3 + zod: 4.1.12 + + '@tailwindcss/node@4.1.14': dependencies: '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.18.3 - jiti: 2.6.0 + jiti: 2.6.1 lightningcss: 1.30.1 magic-string: 0.30.19 source-map-js: 1.2.1 - tailwindcss: 4.1.13 + tailwindcss: 4.1.14 - '@tailwindcss/oxide-android-arm64@4.1.13': + '@tailwindcss/oxide-android-arm64@4.1.14': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.13': + '@tailwindcss/oxide-darwin-arm64@4.1.14': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.13': + '@tailwindcss/oxide-darwin-x64@4.1.14': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.13': + '@tailwindcss/oxide-freebsd-x64@4.1.14': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.13': + '@tailwindcss/oxide-linux-x64-musl@4.1.14': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.13': + '@tailwindcss/oxide-wasm32-wasi@4.1.14': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': optional: true - '@tailwindcss/oxide@4.1.13': + '@tailwindcss/oxide@4.1.14': dependencies: - detect-libc: 2.1.1 + detect-libc: 2.1.2 tar: 7.5.1 optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.13 - '@tailwindcss/oxide-darwin-arm64': 4.1.13 - '@tailwindcss/oxide-darwin-x64': 4.1.13 - '@tailwindcss/oxide-freebsd-x64': 4.1.13 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.13 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.13 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.13 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.13 - '@tailwindcss/oxide-linux-x64-musl': 4.1.13 - '@tailwindcss/oxide-wasm32-wasi': 4.1.13 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.13 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.13 + '@tailwindcss/oxide-android-arm64': 4.1.14 + '@tailwindcss/oxide-darwin-arm64': 4.1.14 + '@tailwindcss/oxide-darwin-x64': 4.1.14 + '@tailwindcss/oxide-freebsd-x64': 4.1.14 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.14 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.14 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.14 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.14 + '@tailwindcss/oxide-linux-x64-musl': 4.1.14 + '@tailwindcss/oxide-wasm32-wasi': 4.1.14 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.14 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.14 - '@tailwindcss/postcss@4.1.13': + '@tailwindcss/postcss@4.1.14': dependencies: '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.13 - '@tailwindcss/oxide': 4.1.13 + '@tailwindcss/node': 4.1.14 + '@tailwindcss/oxide': 4.1.14 postcss: 8.5.6 - tailwindcss: 4.1.13 + tailwindcss: 4.1.14 '@tybys/wasm-util@0.10.1': dependencies: @@ -3010,7 +3121,7 @@ snapshots: '@types/cors@2.8.19': dependencies: - '@types/node': 20.19.17 + '@types/node': 20.19.22 '@types/estree@1.0.8': {} @@ -3018,109 +3129,165 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@20.19.17': + '@types/node@20.19.22': dependencies: undici-types: 6.21.0 - '@types/react-dom@19.1.9(@types/react@19.1.15)': + '@types/node@24.8.1': dependencies: - '@types/react': 19.1.15 + undici-types: 7.14.0 - '@types/react@19.1.15': + '@types/react-dom@19.2.2(@types/react@19.2.2)': + dependencies: + '@types/react': 19.2.2 + + '@types/react@19.2.2': dependencies: csstype: 3.1.3 - '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.45.0 - eslint: 9.36.0(jiti@2.6.0) + '@typescript-eslint/parser': 8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/type-utils': 8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 + eslint: 9.36.0(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.45.0 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/type-utils': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 + eslint: 9.38.0(jiti@2.6.1) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.0) - typescript: 5.9.2 + eslint: 9.36.0(jiti@2.6.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.45.0(typescript@5.9.2)': + '@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.2) - '@typescript-eslint/types': 8.45.0 + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 debug: 4.4.3 - typescript: 5.9.2 + eslint: 9.38.0(jiti@2.6.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.45.0': + '@typescript-eslint/project-service@8.46.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/visitor-keys': 8.45.0 - - '@typescript-eslint/tsconfig-utils@8.45.0(typescript@5.9.2)': - dependencies: - typescript: 5.9.2 - - '@typescript-eslint/type-utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': - dependencies: - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3) + '@typescript-eslint/types': 8.46.1 debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.0) - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.45.0': {} - - '@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.2)': + '@typescript-eslint/scope-manager@8.46.1': dependencies: - '@typescript-eslint/project-service': 8.45.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.2) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/visitor-keys': 8.45.0 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/visitor-keys': 8.46.1 + + '@typescript-eslint/tsconfig-utils@8.46.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.36.0(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.38.0(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.46.1': {} + + '@typescript-eslint/typescript-estree@8.46.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.46.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3) + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/visitor-keys': 8.46.1 debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/utils@8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) - eslint: 9.36.0(jiti@2.6.0) - typescript: 5.9.2 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + eslint: 9.36.0(jiti@2.6.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.45.0': + '@typescript-eslint/utils@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.45.0 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + eslint: 9.38.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.46.1': + dependencies: + '@typescript-eslint/types': 8.46.1 eslint-visitor-keys: 4.2.1 '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -3283,7 +3450,7 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axe-core@4.10.3: {} + axe-core@4.11.0: {} axobject-query@4.1.0: {} @@ -3291,7 +3458,7 @@ snapshots: base64id@2.0.0: {} - baseline-browser-mapping@2.8.12: {} + baseline-browser-mapping@2.8.18: {} brace-expansion@1.1.12: dependencies: @@ -3308,10 +3475,10 @@ snapshots: browserslist@4.26.3: dependencies: - baseline-browser-mapping: 2.8.12 - caniuse-lite: 1.0.30001747 - electron-to-chromium: 1.5.230 - node-releases: 2.0.23 + baseline-browser-mapping: 2.8.18 + caniuse-lite: 1.0.30001751 + electron-to-chromium: 1.5.237 + node-releases: 2.0.25 update-browserslist-db: 1.1.3(browserslist@4.26.3) call-bind-apply-helpers@1.0.2: @@ -3333,9 +3500,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001745: {} - - caniuse-lite@1.0.30001747: {} + caniuse-lite@1.0.30001751: {} chalk@4.1.2: dependencies: @@ -3393,6 +3558,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + dayjs@1.11.18: {} + debug@3.2.7: dependencies: ms: 2.1.3 @@ -3419,7 +3586,7 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - detect-libc@2.1.1: {} + detect-libc@2.1.2: {} doctrine@2.1.0: dependencies: @@ -3446,7 +3613,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.230: {} + electron-to-chromium@1.5.237: {} emoji-regex@9.2.2: {} @@ -3467,7 +3634,7 @@ snapshots: engine.io@6.6.4: dependencies: '@types/cors': 2.8.19 - '@types/node': 20.19.17 + '@types/node': 20.19.22 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -3483,7 +3650,7 @@ snapshots: enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.3 + tapable: 2.3.0 es-abstract@1.24.0: dependencies: @@ -3619,21 +3786,21 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@15.5.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + eslint-config-next@15.5.4(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 15.5.4 - '@rushstack/eslint-patch': 1.12.0 - '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint: 9.36.0(jiti@2.6.0) + '@rushstack/eslint-patch': 1.14.0 + '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.38.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-react: 7.37.5(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.36.0(jiti@2.6.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-react: 7.37.5(eslint@9.38.0(jiti@2.6.1)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.38.0(jiti@2.6.1)) optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - eslint-import-resolver-webpack - eslint-plugin-import-x @@ -3647,33 +3814,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.36.0(jiti@2.6.0)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.0) - get-tsconfig: 4.10.1 + eslint: 9.38.0(jiti@2.6.1) + get-tsconfig: 4.12.0 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint: 9.36.0(jiti@2.6.0) + '@typescript-eslint/parser': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.38.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.36.0(jiti@2.6.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.38.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -3682,9 +3849,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.38.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0(jiti@2.6.0)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.38.0(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -3696,23 +3863,23 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/parser': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.38.0(jiti@2.6.1)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 - axe-core: 4.10.3 + axe-core: 4.11.0 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.38.0(jiti@2.6.1) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -3721,21 +3888,21 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.2.0(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-react-hooks@5.2.0(eslint@9.38.0(jiti@2.6.1)): dependencies: - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.38.0(jiti@2.6.1) - eslint-plugin-react-hooks@6.1.1(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-react-hooks@6.1.1(eslint@9.38.0(jiti@2.6.1)): dependencies: '@babel/core': 7.28.4 '@babel/parser': 7.28.4 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.38.0(jiti@2.6.1) zod: 4.1.12 zod-validation-error: 4.0.2(zod@4.1.12) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-react@7.37.5(eslint@9.38.0(jiti@2.6.1)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -3743,7 +3910,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.36.0(jiti@2.6.0) + eslint: 9.38.0(jiti@2.6.1) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -3766,9 +3933,9 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.36.0(jiti@2.6.0): + eslint@9.36.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.1 @@ -3804,7 +3971,48 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.0 + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + eslint@9.38.0(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.1 + '@eslint/core': 0.16.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.38.0 + '@eslint/plugin-kit': 0.4.0 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 transitivePeerDependencies: - supports-color @@ -3896,6 +4104,8 @@ snapshots: functions-have-names@1.2.3: {} + generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} get-intrinsic@1.3.0: @@ -3926,6 +4136,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@4.12.0: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3943,7 +4157,7 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 - goober@2.1.16(csstype@3.1.3): + goober@2.1.18(csstype@3.1.3): dependencies: csstype: 3.1.3 @@ -4019,7 +4233,7 @@ snapshots: is-bun-module@2.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 is-callable@1.2.7: {} @@ -4044,9 +4258,10 @@ snapshots: dependencies: call-bound: 1.0.4 - is-generator-function@1.1.0: + is-generator-function@1.1.2: dependencies: call-bound: 1.0.4 + generator-function: 2.0.1 get-proto: 1.0.1 has-tostringtag: 1.0.2 safe-regex-test: 1.1.0 @@ -4118,7 +4333,7 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 - jiti@2.6.0: {} + jiti@2.6.1: {} js-tokens@4.0.0: {} @@ -4151,7 +4366,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - ky@1.11.0: {} + ky@1.12.0: {} language-subtag-registry@0.3.23: {} @@ -4196,7 +4411,7 @@ snapshots: lightningcss@1.30.1: dependencies: - detect-libc: 2.1.1 + detect-libc: 2.1.2 optionalDependencies: lightningcss-darwin-arm64: 1.30.1 lightningcss-darwin-x64: 1.30.1 @@ -4262,7 +4477,7 @@ snapshots: nanoid@3.3.11: {} - napi-postinstall@0.3.3: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -4272,7 +4487,7 @@ snapshots: dependencies: '@next/env': 15.5.4 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001745 + caniuse-lite: 1.0.30001751 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -4291,7 +4506,7 @@ snapshots: - '@babel/core' - babel-plugin-macros - node-releases@2.0.23: {} + node-releases@2.0.25: {} object-assign@4.1.1: {} @@ -4421,7 +4636,7 @@ snapshots: react-hot-toast@2.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: csstype: 3.1.3 - goober: 2.1.16(csstype@3.1.3) + goober: 2.1.18(csstype@3.1.3) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -4496,6 +4711,8 @@ snapshots: semver@7.7.2: {} + semver@7.7.3: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -4521,8 +4738,8 @@ snapshots: sharp@0.34.4: dependencies: '@img/colour': 1.0.0 - detect-libc: 2.1.1 - semver: 7.7.2 + detect-libc: 2.1.2 + semver: 7.7.3 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.4 '@img/sharp-darwin-x64': 0.34.4 @@ -4699,9 +4916,9 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwindcss@4.1.13: {} + tailwindcss@4.1.14: {} - tapable@2.2.3: {} + tapable@2.3.0: {} tar@7.5.1: dependencies: @@ -4724,9 +4941,9 @@ snapshots: toposort@2.0.2: {} - ts-api-utils@2.1.0(typescript@5.9.2): + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: - typescript: 5.9.2 + typescript: 5.9.3 tsconfig-paths@3.15.0: dependencies: @@ -4785,7 +5002,27 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript@5.9.2: {} + typescript-eslint@8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.36.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript-eslint@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.38.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color typescript@5.9.3: {} @@ -4798,9 +5035,11 @@ snapshots: undici-types@6.21.0: {} + undici-types@7.14.0: {} + unrs-resolver@1.11.1: dependencies: - napi-postinstall: 0.3.3 + napi-postinstall: 0.3.4 optionalDependencies: '@unrs/resolver-binding-android-arm-eabi': 1.11.1 '@unrs/resolver-binding-android-arm64': 1.11.1 @@ -4857,7 +5096,7 @@ snapshots: is-async-function: 2.1.1 is-date-object: 1.1.0 is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.0 + is-generator-function: 1.1.2 is-regex: 1.2.1 is-weakref: 1.1.1 isarray: 2.0.5 @@ -4915,7 +5154,7 @@ snapshots: zod@4.1.12: {} - zustand@5.0.8(@types/react@19.1.15)(react@19.1.0): + zustand@5.0.8(@types/react@19.2.2)(react@19.1.0): optionalDependencies: - '@types/react': 19.1.15 + '@types/react': 19.2.2 react: 19.1.0