diff --git a/.gitignore b/.gitignore index c058940..e9e17a1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ # next.js /.next/ /out/ +certificates/ # production /build diff --git a/API.md b/API.md deleted file mode 100644 index eb18ec5..0000000 --- a/API.md +++ /dev/null @@ -1,130 +0,0 @@ -# TomodachiShare API Reference - -Welcome to the TomodachiShare API Reference! -Some routes may require authentication (see [Protected](#protected-endpoints) section - _TODO_). - -## Public Endpoints - -### **Search Miis** - -`GET /api/search?q={query}` - -Searches Miis by name, tags, and description. - -#### **Query Parameters** - -| Name | Type | Required | Description | -| ------ | ------ | -------- | ----------------------------------------------------------------- | -| **q** | string | **Yes** | The text to search for. Matches names, tags, and descriptions. | -| sort | string | No | Sorting mode: `likes`, `newest`, `oldest`, or `random`. | -| tags | string | No | Comma-separated list of tags. Example: `anime,frieren`. | -| gender | string | No | Gender filter: `MALE` or `FEMALE`. | -| limit | number | No | Number of results per page (1-100). | -| page | number | No | Page number. Defaults to `1`. | -| seed | number | No | Seed used for `random` sorting to ensure unique results per page. | - -#### **Examples** - -``` -https://tomodachishare.com/api/search?q=frieren -``` - -``` -https://tomodachishare.com/api/search?q=frieren&sort=random&tags=anime,frieren&gender=MALE&limit=20&page=1&seed=1204 -``` - -#### **Response** - -Returns an array of Mii IDs: - -```json -[1, 204, 295, 1024] -``` - -When no Miis are found: - -```json -{ "error": "No Miis found!" } -``` - ---- - -### **Get Mii Image / QR Code / Metadata Image** - -`GET /mii/{id}/image?type={type}` - -Retrieves the Mii image, QR code, or metadata graphic. - -#### **Path & Query Parameters** - -| Name | Type | Required | Description | -| -------- | ------ | -------- | ------------------------------------- | -| **id** | number | **Yes** | The Mii’s ID. | -| **type** | string | **Yes** | One of: `mii`, `qr-code`, `metadata`. | - -#### **Examples** - -``` -https://tomodachishare.com/mii/1/image?type=mii -``` - -``` -https://tomodachishare.com/mii/2/image?type=qr-code -``` - -``` -https://tomodachishare.com/mii/3/image?type=metadata -``` - -#### **Response** - -Returns the image file. - ---- - -### **Get Mii Data** - -`GET /mii/{id}/data` - -Fetches metadata for a specific Mii. - -#### **Path Parameters** - -| Name | Type | Required | Description | -| ------ | ------ | -------- | ------------- | -| **id** | number | **Yes** | The Mii’s ID. | - -#### **Example** - -``` -https://tomodachishare.com/mii/1/data -``` - -#### **Response** - -```json -{ - "id": 1, - "name": "Frieren", - "platform": "THREE_DS", - "imageCount": 3, - "tags": ["anime", "frieren"], - "description": "Frieren from 'Frieren: Beyond Journey's End'\r\nThe first Mii on the site!", - "firstName": "Frieren", - "lastName": "the Slayer", - "gender": "FEMALE", - "islandName": "Wuhu", - "allowedCopying": false, - "createdAt": "2025-05-04T12:29:41Z", - "user": { - "id": 1, - "username": "trafficlunar", - "name": "trafficlunar" - }, - "likes": 29 -} -``` - -## Protected Endpoints - -_TODO_ diff --git a/README.md b/README.md index 54cd8bf..69d0a31 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ ## About -**TomodachiShare** is a fan-made website that lets you easily discover, upload, and share **Mii characters** for the game **Tomodachi Life**. +TomodachiShare is a fan-made website that lets you easily discover, upload, and share Mii characters for the game Tomodachi Life. - 📷 Upload or scan your Mii QR codes - ✨ Generates Mii renders for previews @@ -27,8 +27,6 @@ ### Development Instructions -### API Reference - ---

diff --git a/package.json b/package.json index a6482e0..5299718 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tomodachi-share", "version": "0.1.0", "private": true, - "packageManager": "pnpm@10.24.0", + "packageManager": "pnpm@10.28.2", "scripts": { "dev": "next dev", "build": "next build", @@ -16,45 +16,44 @@ "@auth/prisma-adapter": "2.11.1", "@bprogress/next": "^3.2.12", "@hello-pangea/dnd": "^18.0.1", - "@prisma/client": "^6.19.1", - "bit-buffer": "^0.2.5", + "@prisma/client": "^6.19.2", + "bit-buffer": "^0.3.0", "canvas-confetti": "^1.9.4", "dayjs": "^1.11.19", "downshift": "^9.0.13", "embla-carousel-react": "^8.6.0", - "file-type": "^21.1.1", - "ioredis": "^5.8.2", + "file-type": "^21.3.0", "jsqr": "^1.4.0", - "next": "16.0.10", + "next": "16.1.6", "next-auth": "5.0.0-beta.30", "qrcode-generator": "^2.0.4", - "react": "^19.2.3", - "react-dom": "^19.2.3", + "react": "^19.2.4", + "react-dom": "^19.2.4", "react-dropzone": "^14.3.8", - "react-webcam": "^7.2.0", - "satori": "^0.18.3", + "redis": "^5.10.0", + "satori": "^0.19.1", "seedrandom": "^3.0.5", "sharp": "^0.34.5", "sjcl-with-all": "1.0.8", - "swr": "^2.3.7", - "zod": "^4.1.13" + "swr": "^2.3.8", + "zod": "^4.3.6" }, "devDependencies": { "@eslint/eslintrc": "^3.3.3", "@iconify/react": "^6.0.2", "@tailwindcss/postcss": "^4.1.18", "@types/canvas-confetti": "^1.9.0", - "@types/node": "^25.0.2", - "@types/react": "^19.2.7", + "@types/node": "^25.1.0", + "@types/react": "^19.2.10", "@types/react-dom": "^19.2.3", "@types/seedrandom": "^3.0.8", "@types/sjcl": "^1.0.34", "eslint": "^9.39.2", - "eslint-config-next": "16.0.10", - "prisma": "^6.19.1", + "eslint-config-next": "16.1.6", + "prisma": "^6.19.2", "schema-dts": "^1.1.5", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", - "vitest": "^4.0.15" + "vitest": "^4.0.18" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43d2dfb..c8dd795 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,19 +13,19 @@ importers: version: 3.2.0 '@auth/prisma-adapter': specifier: 2.11.1 - version: 2.11.1(@prisma/client@6.19.1(prisma@6.19.1(typescript@5.9.3))(typescript@5.9.3)) + version: 2.11.1(@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)) '@bprogress/next': specifier: ^3.2.12 - version: 3.2.12(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 3.2.12(next@16.1.6(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@hello-pangea/dnd': specifier: ^18.0.1 - version: 18.0.1(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 18.0.1(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@prisma/client': - specifier: ^6.19.1 - version: 6.19.1(prisma@6.19.1(typescript@5.9.3))(typescript@5.9.3) + specifier: ^6.19.2 + version: 6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3) bit-buffer: - specifier: ^0.2.5 - version: 0.2.5 + specifier: ^0.3.0 + version: 0.3.0 canvas-confetti: specifier: ^1.9.4 version: 1.9.4 @@ -34,43 +34,40 @@ importers: version: 1.11.19 downshift: specifier: ^9.0.13 - version: 9.0.13(react@19.2.3) + version: 9.0.13(react@19.2.4) embla-carousel-react: specifier: ^8.6.0 - version: 8.6.0(react@19.2.3) + version: 8.6.0(react@19.2.4) file-type: - specifier: ^21.1.1 - version: 21.1.1 - ioredis: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^21.3.0 + version: 21.3.0 jsqr: specifier: ^1.4.0 version: 1.4.0 next: - specifier: 16.0.10 - version: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: 16.1.6 + version: 16.1.6(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-auth: specifier: 5.0.0-beta.30 - version: 5.0.0-beta.30(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) + version: 5.0.0-beta.30(next@16.1.6(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) qrcode-generator: specifier: ^2.0.4 version: 2.0.4 react: - specifier: ^19.2.3 - version: 19.2.3 + specifier: ^19.2.4 + version: 19.2.4 react-dom: - specifier: ^19.2.3 - version: 19.2.3(react@19.2.3) + specifier: ^19.2.4 + version: 19.2.4(react@19.2.4) react-dropzone: specifier: ^14.3.8 - version: 14.3.8(react@19.2.3) - react-webcam: - specifier: ^7.2.0 - version: 7.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 14.3.8(react@19.2.4) + redis: + specifier: ^5.10.0 + version: 5.10.0 satori: - specifier: ^0.18.3 - version: 0.18.3 + specifier: ^0.19.1 + version: 0.19.1 seedrandom: specifier: ^3.0.5 version: 3.0.5 @@ -81,18 +78,18 @@ importers: specifier: 1.0.8 version: 1.0.8 swr: - specifier: ^2.3.7 - version: 2.3.7(react@19.2.3) + specifier: ^2.3.8 + version: 2.3.8(react@19.2.4) zod: - specifier: ^4.1.13 - version: 4.1.13 + specifier: ^4.3.6 + version: 4.3.6 devDependencies: '@eslint/eslintrc': specifier: ^3.3.3 version: 3.3.3 '@iconify/react': specifier: ^6.0.2 - version: 6.0.2(react@19.2.3) + version: 6.0.2(react@19.2.4) '@tailwindcss/postcss': specifier: ^4.1.18 version: 4.1.18 @@ -100,14 +97,14 @@ importers: specifier: ^1.9.0 version: 1.9.0 '@types/node': - specifier: ^25.0.2 - version: 25.0.2 + specifier: ^25.1.0 + version: 25.1.0 '@types/react': - specifier: ^19.2.7 - version: 19.2.7 + specifier: ^19.2.10 + version: 19.2.10 '@types/react-dom': specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.7) + version: 19.2.3(@types/react@19.2.10) '@types/seedrandom': specifier: ^3.0.8 version: 3.0.8 @@ -118,11 +115,11 @@ importers: specifier: ^9.39.2 version: 9.39.2(jiti@2.6.1) eslint-config-next: - specifier: 16.0.10 - version: 16.0.10(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: 16.1.6 + version: 16.1.6(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) prisma: - specifier: ^6.19.1 - version: 6.19.1(typescript@5.9.3) + specifier: ^6.19.2 + version: 6.19.2(typescript@5.9.3) schema-dts: specifier: ^1.1.5 version: 1.1.5 @@ -133,8 +130,8 @@ importers: specifier: ^5.9.3 version: 5.9.3 vitest: - specifier: ^4.0.15 - version: 4.0.15(@types/node@25.0.2)(jiti@2.6.1)(lightningcss@1.30.2) + specifier: ^4.0.18 + version: 4.0.18(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2) packages: @@ -179,36 +176,36 @@ packages: peerDependencies: '@prisma/client': '>=2.26.0 || >=3 || >=4 || >=5 || >=6' - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -225,33 +222,33 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} engines: {node: '>=6.9.0'} - '@borewit/text-codec@0.1.1': - resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + '@borewit/text-codec@0.2.1': + resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} '@bprogress/core@1.3.4': resolution: {integrity: sha512-q/AqpurI/1uJzOrQROuZWixn/+ARekh+uvJGwLCP6HQ/EqAX4SkvNf618tSBxL4NysC0MwqAppb/mRw6Tzi61w==} @@ -269,173 +266,173 @@ packages: react: '>=18.0.0' react-dom: '>=18.0.0' - '@emnapi/core@1.7.1': - resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} - '@emnapi/runtime@1.7.1': - resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.0': - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -532,89 +529,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -639,9 +652,6 @@ packages: cpu: [x64] os: [win32] - '@ioredis/commands@1.4.0': - resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -661,56 +671,60 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@next/env@16.0.10': - resolution: {integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==} + '@next/env@16.1.6': + resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==} - '@next/eslint-plugin-next@16.0.10': - resolution: {integrity: sha512-b2NlWN70bbPLmfyoLvvidPKWENBYYIe017ZGUpElvQjDytCWgxPJx7L9juxHt0xHvNVA08ZHJdOyhGzon/KJuw==} + '@next/eslint-plugin-next@16.1.6': + resolution: {integrity: sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==} - '@next/swc-darwin-arm64@16.0.10': - resolution: {integrity: sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==} + '@next/swc-darwin-arm64@16.1.6': + resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.0.10': - resolution: {integrity: sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==} + '@next/swc-darwin-x64@16.1.6': + resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.0.10': - resolution: {integrity: sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==} + '@next/swc-linux-arm64-gnu@16.1.6': + resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] - '@next/swc-linux-arm64-musl@16.0.10': - resolution: {integrity: sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==} + '@next/swc-linux-arm64-musl@16.1.6': + resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] - '@next/swc-linux-x64-gnu@16.0.10': - resolution: {integrity: sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==} + '@next/swc-linux-x64-gnu@16.1.6': + resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] - '@next/swc-linux-x64-musl@16.0.10': - resolution: {integrity: sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==} + '@next/swc-linux-x64-musl@16.1.6': + resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] - '@next/swc-win32-arm64-msvc@16.0.10': - resolution: {integrity: sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==} + '@next/swc-win32-arm64-msvc@16.1.6': + resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.0.10': - resolution: {integrity: sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==} + '@next/swc-win32-x64-msvc@16.1.6': + resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -734,8 +748,8 @@ packages: '@panva/hkdf@1.2.1': resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} - '@prisma/client@6.19.1': - resolution: {integrity: sha512-4SXj4Oo6HyQkLUWT8Ke5R0PTAfVOKip5Roo+6+b2EDTkFg5be0FnBWiuRJc0BC0sRQIWGMLKW1XguhVfW/z3/A==} + '@prisma/client@6.19.2': + resolution: {integrity: sha512-gR2EMvfK/aTxsuooaDA32D8v+us/8AAet+C3J1cc04SW35FPdZYgLF+iN4NDLUgAaUGTKdAB0CYenu1TAgGdMg==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -746,131 +760,187 @@ packages: typescript: optional: true - '@prisma/config@6.19.1': - resolution: {integrity: sha512-bUL/aYkGXLwxVGhJmQMtslLT7KPEfUqmRa919fKI4wQFX4bIFUKiY8Jmio/2waAjjPYrtuDHa7EsNCnJTXxiOw==} + '@prisma/config@6.19.2': + resolution: {integrity: sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ==} - '@prisma/debug@6.19.1': - resolution: {integrity: sha512-h1JImhlAd/s5nhY/e9qkAzausWldbeT+e4nZF7A4zjDYBF4BZmKDt4y0jK7EZapqOm1kW7V0e9agV/iFDy3fWw==} + '@prisma/debug@6.19.2': + resolution: {integrity: sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==} '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': resolution: {integrity: sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==} - '@prisma/engines@6.19.1': - resolution: {integrity: sha512-xy95dNJ7DiPf9IJ3oaVfX785nbFl7oNDzclUF+DIiJw6WdWCvPl0LPU0YqQLsrwv8N64uOQkH391ujo3wSo+Nw==} + '@prisma/engines@6.19.2': + resolution: {integrity: sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A==} - '@prisma/fetch-engine@6.19.1': - resolution: {integrity: sha512-mmgcotdaq4VtAHO6keov3db+hqlBzQS6X7tR7dFCbvXjLVTxBYdSJFRWz+dq7F9p6dvWyy1X0v8BlfRixyQK6g==} + '@prisma/fetch-engine@6.19.2': + resolution: {integrity: sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q==} - '@prisma/get-platform@6.19.1': - resolution: {integrity: sha512-zsg44QUiQAnFUyh6Fbt7c9HjMXHwFTqtrgcX7DAZmRgnkPyYT7Sh8Mn8D5PuuDYNtMOYcpLGg576MLfIORsBYw==} + '@prisma/get-platform@6.19.2': + resolution: {integrity: sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA==} - '@rollup/rollup-android-arm-eabi@4.53.3': - resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + '@redis/bloom@5.10.0': + resolution: {integrity: sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A==} + engines: {node: '>= 18'} + peerDependencies: + '@redis/client': ^5.10.0 + + '@redis/client@5.10.0': + resolution: {integrity: sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==} + engines: {node: '>= 18'} + + '@redis/json@5.10.0': + resolution: {integrity: sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA==} + engines: {node: '>= 18'} + peerDependencies: + '@redis/client': ^5.10.0 + + '@redis/search@5.10.0': + resolution: {integrity: sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg==} + engines: {node: '>= 18'} + peerDependencies: + '@redis/client': ^5.10.0 + + '@redis/time-series@5.10.0': + resolution: {integrity: sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg==} + engines: {node: '>= 18'} + peerDependencies: + '@redis/client': ^5.10.0 + + '@rollup/rollup-android-arm-eabi@4.57.0': + resolution: {integrity: sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.53.3': - resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + '@rollup/rollup-android-arm64@4.57.0': + resolution: {integrity: sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.53.3': - resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + '@rollup/rollup-darwin-arm64@4.57.0': + resolution: {integrity: sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.53.3': - resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + '@rollup/rollup-darwin-x64@4.57.0': + resolution: {integrity: sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.53.3': - resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + '@rollup/rollup-freebsd-arm64@4.57.0': + resolution: {integrity: sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.53.3': - resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + '@rollup/rollup-freebsd-x64@4.57.0': + resolution: {integrity: sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.53.3': - resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + '@rollup/rollup-linux-arm-gnueabihf@4.57.0': + resolution: {integrity: sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==} cpu: [arm] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.53.3': - resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + '@rollup/rollup-linux-arm-musleabihf@4.57.0': + resolution: {integrity: sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==} cpu: [arm] os: [linux] + libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.53.3': - resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + '@rollup/rollup-linux-arm64-gnu@4.57.0': + resolution: {integrity: sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.53.3': - resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + '@rollup/rollup-linux-arm64-musl@4.57.0': + resolution: {integrity: sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.53.3': - resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + '@rollup/rollup-linux-loong64-gnu@4.57.0': + resolution: {integrity: sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==} cpu: [loong64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.53.3': - resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + '@rollup/rollup-linux-loong64-musl@4.57.0': + resolution: {integrity: sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.57.0': + resolution: {integrity: sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.53.3': - resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + '@rollup/rollup-linux-ppc64-musl@4.57.0': + resolution: {integrity: sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.57.0': + resolution: {integrity: sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.53.3': - resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + '@rollup/rollup-linux-riscv64-musl@4.57.0': + resolution: {integrity: sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==} cpu: [riscv64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.53.3': - resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + '@rollup/rollup-linux-s390x-gnu@4.57.0': + resolution: {integrity: sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.53.3': - resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + '@rollup/rollup-linux-x64-gnu@4.57.0': + resolution: {integrity: sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.53.3': - resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + '@rollup/rollup-linux-x64-musl@4.57.0': + resolution: {integrity: sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==} cpu: [x64] os: [linux] + libc: [musl] - '@rollup/rollup-openharmony-arm64@4.53.3': - resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + '@rollup/rollup-openbsd-x64@4.57.0': + resolution: {integrity: sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.0': + resolution: {integrity: sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.53.3': - resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + '@rollup/rollup-win32-arm64-msvc@4.57.0': + resolution: {integrity: sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.53.3': - resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + '@rollup/rollup-win32-ia32-msvc@4.57.0': + resolution: {integrity: sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.53.3': - resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + '@rollup/rollup-win32-x64-gnu@4.57.0': + resolution: {integrity: sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.53.3': - resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + '@rollup/rollup-win32-x64-msvc@4.57.0': + resolution: {integrity: sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==} cpu: [x64] os: [win32] @@ -882,8 +952,8 @@ packages: engines: {node: '>= 8.0.0'} hasBin: true - '@standard-schema/spec@1.0.0': - resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -926,24 +996,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -1004,16 +1078,16 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@25.0.2': - resolution: {integrity: sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==} + '@types/node@25.1.0': + resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==} '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.7': - resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + '@types/react@19.2.10': + resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==} '@types/seedrandom@3.0.8': resolution: {integrity: sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==} @@ -1024,63 +1098,63 @@ packages: '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - '@typescript-eslint/eslint-plugin@8.49.0': - resolution: {integrity: sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==} + '@typescript-eslint/eslint-plugin@8.54.0': + resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.49.0 + '@typescript-eslint/parser': ^8.54.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.49.0': - resolution: {integrity: sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==} + '@typescript-eslint/parser@8.54.0': + resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} 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.49.0': - resolution: {integrity: sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==} + '@typescript-eslint/project-service@8.54.0': + resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.49.0': - resolution: {integrity: sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==} + '@typescript-eslint/scope-manager@8.54.0': + resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.49.0': - resolution: {integrity: sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==} + '@typescript-eslint/tsconfig-utils@8.54.0': + resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.49.0': - resolution: {integrity: sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==} + '@typescript-eslint/type-utils@8.54.0': + resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} 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.49.0': - resolution: {integrity: sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==} + '@typescript-eslint/types@8.54.0': + resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.49.0': - resolution: {integrity: sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==} + '@typescript-eslint/typescript-estree@8.54.0': + resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.49.0': - resolution: {integrity: sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==} + '@typescript-eslint/utils@8.54.0': + resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} 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.49.0': - resolution: {integrity: sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==} + '@typescript-eslint/visitor-keys@8.54.0': + resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -1122,41 +1196,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -1178,11 +1260,11 @@ packages: cpu: [x64] os: [win32] - '@vitest/expect@4.0.15': - resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==} + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} - '@vitest/mocker@4.0.15': - resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -1192,20 +1274,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.0.15': - resolution: {integrity: sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==} + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} - '@vitest/runner@4.0.15': - resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==} + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} - '@vitest/snapshot@4.0.15': - resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==} + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} - '@vitest/spy@4.0.15': - resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==} + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} - '@vitest/utils@4.0.15': - resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==} + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -1282,8 +1364,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.11.0: - resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} + axe-core@4.11.1: + resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} engines: {node: '>=4'} axobject-query@4.1.0: @@ -1297,12 +1379,12 @@ packages: resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} engines: {node: '>= 0.4'} - baseline-browser-mapping@2.9.7: - resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==} + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true - bit-buffer@0.2.5: - resolution: {integrity: sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==} + bit-buffer@0.3.0: + resolution: {integrity: sha512-ZuHiT8XheCOwON2r23HCmy55TpPeYBe2d6BDdOMuvCmswI+6szog44JeE3sVuaF0K2kLatuXBTQ6WCCcI4KMyw==} brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1346,14 +1428,14 @@ packages: camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - caniuse-lite@1.0.30001760: - resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + caniuse-lite@1.0.30001766: + resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} canvas-confetti@1.9.4: resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==} - chai@6.2.1: - resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} chalk@4.1.2: @@ -1367,6 +1449,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + citty@0.2.0: + resolution: {integrity: sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -1477,10 +1562,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1512,8 +1593,8 @@ packages: effect@3.18.4: resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} - electron-to-chromium@1.5.267: - resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + electron-to-chromium@1.5.279: + resolution: {integrity: sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==} embla-carousel-react@8.6.0: resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} @@ -1578,8 +1659,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} hasBin: true @@ -1594,8 +1675,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@16.0.10: - resolution: {integrity: sha512-BxouZUm0I45K4yjOOIzj24nTi0H2cGo0y7xUmk+Po/PYtJXFBYVDS1BguE7t28efXjKdcN0tmiLivxQy//SsZg==} + eslint-config-next@16.1.6: + resolution: {integrity: sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==} peerDependencies: eslint: '>=9.0.0' typescript: '>=3.3.1' @@ -1694,8 +1775,8 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -1737,8 +1818,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -1760,8 +1841,8 @@ packages: resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==} engines: {node: '>= 12'} - file-type@21.1.1: - resolution: {integrity: sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg==} + file-type@21.3.0: + resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==} engines: {node: '>=20'} fill-range@7.1.1: @@ -1912,10 +1993,6 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - ioredis@5.8.2: - resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} - engines: {node: '>=12.22.0'} - is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -2122,24 +2199,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -2164,12 +2245,6 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - - lodash.isarguments@3.1.0: - resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -2237,8 +2312,8 @@ packages: nodemailer: optional: true - next@16.0.10: - resolution: {integrity: sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==} + next@16.1.6: + resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -2264,9 +2339,9 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - nypm@0.6.2: - resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} - engines: {node: ^14.16.0 || >=16.10.0} + nypm@0.6.4: + resolution: {integrity: sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==} + engines: {node: '>=18'} hasBin: true oauth4webapi@3.8.3: @@ -2394,8 +2469,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prisma@6.19.1: - resolution: {integrity: sha512-XRfmGzh6gtkc/Vq3LqZJcS2884dQQW3UhPo6jNRoiTW95FFQkXFg8vkYEy6og+Pyv0aY7zRQ7Wn1Cvr56XjhQQ==} + prisma@6.19.2: + resolution: {integrity: sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==} engines: {node: '>=18.18'} hasBin: true peerDependencies: @@ -2426,10 +2501,10 @@ packages: rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} - react-dom@19.2.3: - resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: - react: ^19.2.3 + react: ^19.2.4 react-dropzone@14.3.8: resolution: {integrity: sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==} @@ -2455,27 +2530,17 @@ packages: redux: optional: true - react-webcam@7.2.0: - resolution: {integrity: sha512-xkrzYPqa1ag2DP+2Q/kLKBmCIfEx49bVdgCCCcZf88oF+0NPEbkwYk3/s/C7Zy0mhM8k+hpdNkBLzxg8H0aWcg==} - peerDependencies: - react: '>=16.2.0' - react-dom: '>=16.2.0' - - react@19.2.3: - resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - redis-errors@1.2.0: - resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} - engines: {node: '>=4'} - - redis-parser@3.0.0: - resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} - engines: {node: '>=4'} + redis@5.10.0: + resolution: {integrity: sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw==} + engines: {node: '>= 18'} redux@5.0.1: resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} @@ -2508,8 +2573,8 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.53.3: - resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + rollup@4.57.0: + resolution: {integrity: sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2528,8 +2593,8 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} - satori@0.18.3: - resolution: {integrity: sha512-T3DzWNmnrfVmk2gCIlAxLRLbGkfp3K7TyRva+Byyojqu83BNvnMeqVeYRdmUw4TKCsyH4RiQ/KuF/I4yEzgR5A==} + satori@0.19.1: + resolution: {integrity: sha512-/XaT/JiWLfNlgjlQdde4wXB1/6F+FEze9c3OW2QIH0ywsfOrY57YOetgESWyOFHW3JfEQ6dJAo2U9Xwb7+DDAw==} engines: {node: '>=16'} scheduler@0.27.0: @@ -2606,9 +2671,6 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - standard-as-callback@2.1.0: - resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -2675,8 +2737,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - swr@2.3.7: - resolution: {integrity: sha512-ZEquQ82QvalqTxhBVv/DlAg2mbmUjF4UgpPg9wwk4ufb9rQnZXh1iKyyKBqV6bQGu1Ie7L1QwSYO07qFIa1p+g==} + swr@2.3.8: + resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2712,12 +2774,12 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - token-types@6.1.1: - resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -2748,8 +2810,8 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.49.0: - resolution: {integrity: sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==} + typescript-eslint@8.54.0: + resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2777,8 +2839,8 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - update-browserslist-db@1.2.2: - resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2791,8 +2853,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - vite@7.2.7: - resolution: {integrity: sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2831,18 +2893,18 @@ packages: yaml: optional: true - vitest@4.0.15: - resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.15 - '@vitest/browser-preview': 4.0.15 - '@vitest/browser-webdriverio': 4.0.15 - '@vitest/ui': 4.0.15 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -2877,8 +2939,8 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} which@2.0.2: @@ -2911,8 +2973,8 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@4.1.13: - resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} snapshots: @@ -2936,34 +2998,34 @@ snapshots: preact: 10.24.3 preact-render-to-string: 6.5.11(preact@10.24.3) - '@auth/prisma-adapter@2.11.1(@prisma/client@6.19.1(prisma@6.19.1(typescript@5.9.3))(typescript@5.9.3))': + '@auth/prisma-adapter@2.11.1(@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3))': dependencies: '@auth/core': 0.41.1 - '@prisma/client': 6.19.1(prisma@6.19.1(typescript@5.9.3))(typescript@5.9.3) + '@prisma/client': 6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3) transitivePeerDependencies: - '@simplewebauthn/browser' - '@simplewebauthn/server' - nodemailer - '@babel/code-frame@7.27.1': + '@babel/code-frame@7.28.6': dependencies: '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} + '@babel/compat-data@7.28.6': {} - '@babel/core@7.28.5': + '@babel/core@7.28.6': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -2973,17 +3035,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.5': + '@babel/generator@7.28.6': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.2': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.28.5 + '@babel/compat-data': 7.28.6 '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.1 lru-cache: 5.1.1 @@ -2991,19 +3053,19 @@ snapshots: '@babel/helper-globals@7.28.0': {} - '@babel/helper-module-imports@7.27.1': + '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -3013,65 +3075,65 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.4': + '@babel/helpers@7.28.6': dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 - '@babel/parser@7.28.5': + '@babel/parser@7.28.6': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.28.6 - '@babel/runtime@7.28.4': {} + '@babel/runtime@7.28.6': {} - '@babel/template@7.27.2': + '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 - '@babel/traverse@7.28.5': + '@babel/traverse@7.28.6': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.5': + '@babel/types@7.28.6': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@borewit/text-codec@0.1.1': {} + '@borewit/text-codec@0.2.1': {} '@bprogress/core@1.3.4': {} - '@bprogress/next@3.2.12(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@bprogress/next@3.2.12(next@16.1.6(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@bprogress/core': 1.3.4 - '@bprogress/react': 1.2.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - next: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + '@bprogress/react': 1.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next: 16.1.6(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) - '@bprogress/react@1.2.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@bprogress/react@1.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@bprogress/core': 1.3.4 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) - '@emnapi/core@1.7.1': + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.7.1': + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 optional: true @@ -3081,85 +3143,85 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.25.12': + '@esbuild/aix-ppc64@0.27.2': optional: true - '@esbuild/android-arm64@0.25.12': + '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm@0.25.12': + '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-x64@0.25.12': + '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.25.12': + '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-x64@0.25.12': + '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.25.12': + '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.25.12': + '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/linux-arm64@0.25.12': + '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm@0.25.12': + '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-ia32@0.25.12': + '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-loong64@0.25.12': + '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-mips64el@0.25.12': + '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-ppc64@0.25.12': + '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.25.12': + '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-s390x@0.25.12': + '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-x64@0.25.12': + '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/netbsd-arm64@0.25.12': + '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.25.12': + '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-arm64@0.25.12': + '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.25.12': + '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openharmony-arm64@0.25.12': + '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.25.12': + '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/win32-arm64@0.25.12': + '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.25.12': + '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.25.12': + '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.2(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': dependencies: eslint: 9.39.2(jiti@2.6.1) eslint-visitor-keys: 3.4.3 @@ -3205,14 +3267,14 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@hello-pangea/dnd@18.0.1(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@hello-pangea/dnd@18.0.1(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 css-box-model: 1.2.1 raf-schd: 4.0.3 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-redux: 9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1) redux: 5.0.1 transitivePeerDependencies: - '@types/react' @@ -3228,10 +3290,10 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@iconify/react@6.0.2(react@19.2.3)': + '@iconify/react@6.0.2(react@19.2.4)': dependencies: '@iconify/types': 2.0.0 - react: 19.2.3 + react: 19.2.4 '@iconify/types@2.0.0': {} @@ -3319,7 +3381,7 @@ snapshots: '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.7.1 + '@emnapi/runtime': 1.8.1 optional: true '@img/sharp-win32-arm64@0.34.5': @@ -3331,8 +3393,6 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@ioredis/commands@1.4.0': {} - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3354,39 +3414,39 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.7.1 - '@emnapi/runtime': 1.7.1 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@16.0.10': {} + '@next/env@16.1.6': {} - '@next/eslint-plugin-next@16.0.10': + '@next/eslint-plugin-next@16.1.6': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@16.0.10': + '@next/swc-darwin-arm64@16.1.6': optional: true - '@next/swc-darwin-x64@16.0.10': + '@next/swc-darwin-x64@16.1.6': optional: true - '@next/swc-linux-arm64-gnu@16.0.10': + '@next/swc-linux-arm64-gnu@16.1.6': optional: true - '@next/swc-linux-arm64-musl@16.0.10': + '@next/swc-linux-arm64-musl@16.1.6': optional: true - '@next/swc-linux-x64-gnu@16.0.10': + '@next/swc-linux-x64-gnu@16.1.6': optional: true - '@next/swc-linux-x64-musl@16.0.10': + '@next/swc-linux-x64-musl@16.1.6': optional: true - '@next/swc-win32-arm64-msvc@16.0.10': + '@next/swc-win32-arm64-msvc@16.1.6': optional: true - '@next/swc-win32-x64-msvc@16.0.10': + '@next/swc-win32-x64-msvc@16.1.6': optional: true '@nodelib/fs.scandir@2.1.5': @@ -3399,18 +3459,18 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 + fastq: 1.20.1 '@nolyfill/is-core-module@1.0.39': {} '@panva/hkdf@1.2.1': {} - '@prisma/client@6.19.1(prisma@6.19.1(typescript@5.9.3))(typescript@5.9.3)': + '@prisma/client@6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)': optionalDependencies: - prisma: 6.19.1(typescript@5.9.3) + prisma: 6.19.2(typescript@5.9.3) typescript: 5.9.3 - '@prisma/config@6.19.1': + '@prisma/config@6.19.2': dependencies: c12: 3.1.0 deepmerge-ts: 7.1.5 @@ -3419,91 +3479,120 @@ snapshots: transitivePeerDependencies: - magicast - '@prisma/debug@6.19.1': {} + '@prisma/debug@6.19.2': {} '@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7': {} - '@prisma/engines@6.19.1': + '@prisma/engines@6.19.2': dependencies: - '@prisma/debug': 6.19.1 + '@prisma/debug': 6.19.2 '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 - '@prisma/fetch-engine': 6.19.1 - '@prisma/get-platform': 6.19.1 + '@prisma/fetch-engine': 6.19.2 + '@prisma/get-platform': 6.19.2 - '@prisma/fetch-engine@6.19.1': + '@prisma/fetch-engine@6.19.2': dependencies: - '@prisma/debug': 6.19.1 + '@prisma/debug': 6.19.2 '@prisma/engines-version': 7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7 - '@prisma/get-platform': 6.19.1 + '@prisma/get-platform': 6.19.2 - '@prisma/get-platform@6.19.1': + '@prisma/get-platform@6.19.2': dependencies: - '@prisma/debug': 6.19.1 + '@prisma/debug': 6.19.2 - '@rollup/rollup-android-arm-eabi@4.53.3': + '@redis/bloom@5.10.0(@redis/client@5.10.0)': + dependencies: + '@redis/client': 5.10.0 + + '@redis/client@5.10.0': + dependencies: + cluster-key-slot: 1.1.2 + + '@redis/json@5.10.0(@redis/client@5.10.0)': + dependencies: + '@redis/client': 5.10.0 + + '@redis/search@5.10.0(@redis/client@5.10.0)': + dependencies: + '@redis/client': 5.10.0 + + '@redis/time-series@5.10.0(@redis/client@5.10.0)': + dependencies: + '@redis/client': 5.10.0 + + '@rollup/rollup-android-arm-eabi@4.57.0': optional: true - '@rollup/rollup-android-arm64@4.53.3': + '@rollup/rollup-android-arm64@4.57.0': optional: true - '@rollup/rollup-darwin-arm64@4.53.3': + '@rollup/rollup-darwin-arm64@4.57.0': optional: true - '@rollup/rollup-darwin-x64@4.53.3': + '@rollup/rollup-darwin-x64@4.57.0': optional: true - '@rollup/rollup-freebsd-arm64@4.53.3': + '@rollup/rollup-freebsd-arm64@4.57.0': optional: true - '@rollup/rollup-freebsd-x64@4.53.3': + '@rollup/rollup-freebsd-x64@4.57.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + '@rollup/rollup-linux-arm-gnueabihf@4.57.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.53.3': + '@rollup/rollup-linux-arm-musleabihf@4.57.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.53.3': + '@rollup/rollup-linux-arm64-gnu@4.57.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.53.3': + '@rollup/rollup-linux-arm64-musl@4.57.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.53.3': + '@rollup/rollup-linux-loong64-gnu@4.57.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.53.3': + '@rollup/rollup-linux-loong64-musl@4.57.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.53.3': + '@rollup/rollup-linux-ppc64-gnu@4.57.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.53.3': + '@rollup/rollup-linux-ppc64-musl@4.57.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.53.3': + '@rollup/rollup-linux-riscv64-gnu@4.57.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.53.3': + '@rollup/rollup-linux-riscv64-musl@4.57.0': optional: true - '@rollup/rollup-linux-x64-musl@4.53.3': + '@rollup/rollup-linux-s390x-gnu@4.57.0': optional: true - '@rollup/rollup-openharmony-arm64@4.53.3': + '@rollup/rollup-linux-x64-gnu@4.57.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.53.3': + '@rollup/rollup-linux-x64-musl@4.57.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.53.3': + '@rollup/rollup-openbsd-x64@4.57.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.53.3': + '@rollup/rollup-openharmony-arm64@4.57.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.53.3': + '@rollup/rollup-win32-arm64-msvc@4.57.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.0': optional: true '@rtsao/scc@1.1.0': {} @@ -3513,7 +3602,7 @@ snapshots: fflate: 0.7.4 string.prototype.codepointat: 0.2.1 - '@standard-schema/spec@1.0.0': {} + '@standard-schema/spec@1.1.0': {} '@swc/helpers@0.5.15': dependencies: @@ -3591,7 +3680,7 @@ snapshots: '@tokenizer/inflate@0.4.1': dependencies: debug: 4.4.3 - token-types: 6.1.1 + token-types: 6.1.2 transitivePeerDependencies: - supports-color @@ -3617,15 +3706,15 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@25.0.2': + '@types/node@25.1.0': dependencies: undici-types: 7.16.0 - '@types/react-dom@19.2.3(@types/react@19.2.7)': + '@types/react-dom@19.2.3(@types/react@19.2.10)': dependencies: - '@types/react': 19.2.7 + '@types/react': 19.2.10 - '@types/react@19.2.7': + '@types/react@19.2.10': dependencies: csstype: 3.2.3 @@ -3635,95 +3724,95 @@ snapshots: '@types/use-sync-external-store@0.0.6': {} - '@typescript-eslint/eslint-plugin@8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/type-utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.49.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.49.0': + '@typescript-eslint/scope-manager@8.54.0': dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 - '@typescript-eslint/tsconfig-utils@8.49.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.49.0': {} + '@typescript-eslint/types@8.54.0': {} - '@typescript-eslint/typescript-estree@8.49.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.49.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/visitor-keys': 8.49.0 + '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.15 - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.49.0 - '@typescript-eslint/types': 8.49.0 - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.49.0': + '@typescript-eslint/visitor-keys@8.54.0': dependencies: - '@typescript-eslint/types': 8.49.0 + '@typescript-eslint/types': 8.54.0 eslint-visitor-keys: 4.2.1 '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -3785,43 +3874,43 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitest/expect@4.0.15': + '@vitest/expect@4.0.18': dependencies: - '@standard-schema/spec': 1.0.0 + '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.15 - '@vitest/utils': 4.0.15 - chai: 6.2.1 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.15(vite@7.2.7(@types/node@25.0.2)(jiti@2.6.1)(lightningcss@1.30.2))': + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: - '@vitest/spy': 4.0.15 + '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.7(@types/node@25.0.2)(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2) - '@vitest/pretty-format@4.0.15': + '@vitest/pretty-format@4.0.18': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.15': + '@vitest/runner@4.0.18': dependencies: - '@vitest/utils': 4.0.15 + '@vitest/utils': 4.0.18 pathe: 2.0.3 - '@vitest/snapshot@4.0.15': + '@vitest/snapshot@4.0.18': dependencies: - '@vitest/pretty-format': 4.0.15 + '@vitest/pretty-format': 4.0.18 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.15': {} + '@vitest/spy@4.0.18': {} - '@vitest/utils@4.0.15': + '@vitest/utils@4.0.18': dependencies: - '@vitest/pretty-format': 4.0.15 + '@vitest/pretty-format': 4.0.18 tinyrainbow: 3.0.3 acorn-jsx@5.3.2(acorn@8.15.0): @@ -3924,7 +4013,7 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axe-core@4.11.0: {} + axe-core@4.11.1: {} axobject-query@4.1.0: {} @@ -3932,9 +4021,9 @@ snapshots: base64-js@0.0.8: {} - baseline-browser-mapping@2.9.7: {} + baseline-browser-mapping@2.9.19: {} - bit-buffer@0.2.5: {} + bit-buffer@0.3.0: {} brace-expansion@1.1.12: dependencies: @@ -3951,11 +4040,11 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.7 - caniuse-lite: 1.0.30001760 - electron-to-chromium: 1.5.267 + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001766 + electron-to-chromium: 1.5.279 node-releases: 2.0.27 - update-browserslist-db: 1.2.2(browserslist@4.28.1) + update-browserslist-db: 1.2.3(browserslist@4.28.1) c12@3.1.0: dependencies: @@ -3993,11 +4082,11 @@ snapshots: camelize@1.0.1: {} - caniuse-lite@1.0.30001760: {} + caniuse-lite@1.0.30001766: {} canvas-confetti@1.9.4: {} - chai@6.2.1: {} + chai@6.2.2: {} chalk@4.1.2: dependencies: @@ -4012,6 +4101,8 @@ snapshots: dependencies: consola: 3.4.2 + citty@0.2.0: {} + client-only@0.0.1: {} cluster-key-slot@1.1.2: {} @@ -4106,8 +4197,6 @@ snapshots: defu@6.1.4: {} - denque@2.1.0: {} - dequal@2.0.3: {} destr@2.0.5: {} @@ -4120,12 +4209,12 @@ snapshots: dotenv@16.6.1: {} - downshift@9.0.13(react@19.2.3): + downshift@9.0.13(react@19.2.4): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 compute-scroll-into-view: 3.1.1 prop-types: 15.8.1 - react: 19.2.3 + react: 19.2.4 react-is: 18.2.0 tslib: 2.8.1 @@ -4137,16 +4226,16 @@ snapshots: effect@3.18.4: dependencies: - '@standard-schema/spec': 1.0.0 + '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 - electron-to-chromium@1.5.267: {} + electron-to-chromium@1.5.279: {} - embla-carousel-react@8.6.0(react@19.2.3): + embla-carousel-react@8.6.0(react@19.2.4): dependencies: embla-carousel: 8.6.0 embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0) - react: 19.2.3 + react: 19.2.4 embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0): dependencies: @@ -4220,7 +4309,7 @@ snapshots: typed-array-byte-offset: 1.0.4 typed-array-length: 1.0.7 unbox-primitive: 1.1.0 - which-typed-array: 1.1.19 + which-typed-array: 1.1.20 es-define-property@1.0.1: {} @@ -4268,34 +4357,34 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.25.12: + esbuild@0.27.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 escalade@3.2.0: {} @@ -4303,18 +4392,18 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@16.0.10(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + eslint-config-next@16.1.6(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@next/eslint-plugin-next': 16.0.10 + '@next/eslint-plugin-next': 16.1.6 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1)) globals: 16.4.0 - typescript-eslint: 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -4331,7 +4420,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -4342,22 +4431,22 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4368,7 +4457,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4380,7 +4469,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -4392,7 +4481,7 @@ snapshots: array-includes: 3.1.9 array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 - axe-core: 4.11.0 + axe-core: 4.11.1 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 @@ -4407,12 +4496,12 @@ snapshots: eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)): dependencies: - '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 eslint: 9.39.2(jiti@2.6.1) hermes-parser: 0.25.1 - zod: 4.1.13 - zod-validation-error: 4.0.2(zod@4.1.13) + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) transitivePeerDependencies: - supports-color @@ -4449,7 +4538,7 @@ snapshots: eslint@9.39.2(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 @@ -4469,7 +4558,7 @@ snapshots: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 - esquery: 1.6.0 + esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 @@ -4494,7 +4583,7 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 - esquery@1.6.0: + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -4532,7 +4621,7 @@ snapshots: fast-levenshtein@2.0.6: {} - fastq@1.19.1: + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -4550,11 +4639,11 @@ snapshots: dependencies: tslib: 2.8.1 - file-type@21.1.1: + file-type@21.3.0: dependencies: '@tokenizer/inflate': 0.4.1 strtok3: 10.3.4 - token-types: 6.1.1 + token-types: 6.1.2 uint8array-extras: 1.5.0 transitivePeerDependencies: - supports-color @@ -4633,7 +4722,7 @@ snapshots: consola: 3.4.2 defu: 6.1.4 node-fetch-native: 1.6.7 - nypm: 0.6.2 + nypm: 0.6.4 pathe: 2.0.3 glob-parent@5.1.2: @@ -4706,20 +4795,6 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - ioredis@5.8.2: - dependencies: - '@ioredis/commands': 1.4.0 - cluster-key-slot: 1.1.2 - debug: 4.4.3 - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -4819,7 +4894,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.19 + which-typed-array: 1.1.20 is-weakmap@2.0.2: {} @@ -4951,10 +5026,6 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.defaults@4.2.0: {} - - lodash.isarguments@3.1.0: {} - lodash.merge@4.6.2: {} loose-envify@1.4.0: @@ -4996,30 +5067,31 @@ snapshots: natural-compare@1.4.0: {} - next-auth@5.0.0-beta.30(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3): + next-auth@5.0.0-beta.30(next@16.1.6(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4): dependencies: '@auth/core': 0.41.0 - next: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - react: 19.2.3 + next: 16.1.6(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 - next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next@16.1.6(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: - '@next/env': 16.0.10 + '@next/env': 16.1.6 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001760 + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001766 postcss: 8.4.31 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.3) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + styled-jsx: 5.1.6(@babel/core@7.28.6)(react@19.2.4) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.10 - '@next/swc-darwin-x64': 16.0.10 - '@next/swc-linux-arm64-gnu': 16.0.10 - '@next/swc-linux-arm64-musl': 16.0.10 - '@next/swc-linux-x64-gnu': 16.0.10 - '@next/swc-linux-x64-musl': 16.0.10 - '@next/swc-win32-arm64-msvc': 16.0.10 - '@next/swc-win32-x64-msvc': 16.0.10 + '@next/swc-darwin-arm64': 16.1.6 + '@next/swc-darwin-x64': 16.1.6 + '@next/swc-linux-arm64-gnu': 16.1.6 + '@next/swc-linux-arm64-musl': 16.1.6 + '@next/swc-linux-x64-gnu': 16.1.6 + '@next/swc-linux-x64-musl': 16.1.6 + '@next/swc-win32-arm64-msvc': 16.1.6 + '@next/swc-win32-x64-msvc': 16.1.6 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -5029,12 +5101,10 @@ snapshots: node-releases@2.0.27: {} - nypm@0.6.2: + nypm@0.6.4: dependencies: - citty: 0.1.6 - consola: 3.4.2 + citty: 0.2.0 pathe: 2.0.3 - pkg-types: 2.3.0 tinyexec: 1.0.2 oauth4webapi@3.8.3: {} @@ -5165,10 +5235,10 @@ snapshots: prelude-ls@1.2.1: {} - prisma@6.19.1(typescript@5.9.3): + prisma@6.19.2(typescript@5.9.3): dependencies: - '@prisma/config': 6.19.1 - '@prisma/engines': 6.19.1 + '@prisma/config': 6.19.2 + '@prisma/engines': 6.19.2 optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -5195,45 +5265,42 @@ snapshots: defu: 6.1.4 destr: 2.0.5 - react-dom@19.2.3(react@19.2.3): + react-dom@19.2.4(react@19.2.4): dependencies: - react: 19.2.3 + react: 19.2.4 scheduler: 0.27.0 - react-dropzone@14.3.8(react@19.2.3): + react-dropzone@14.3.8(react@19.2.4): dependencies: attr-accept: 2.2.5 file-selector: 2.1.2 prop-types: 15.8.1 - react: 19.2.3 + react: 19.2.4 react-is@16.13.1: {} react-is@18.2.0: {} - react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 - react: 19.2.3 - use-sync-external-store: 1.6.0(react@19.2.3) + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: - '@types/react': 19.2.7 + '@types/react': 19.2.10 redux: 5.0.1 - react-webcam@7.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): - dependencies: - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - - react@19.2.3: {} + react@19.2.4: {} readdirp@4.1.2: {} - redis-errors@1.2.0: {} - - redis-parser@3.0.0: + redis@5.10.0: dependencies: - redis-errors: 1.2.0 + '@redis/bloom': 5.10.0(@redis/client@5.10.0) + '@redis/client': 5.10.0 + '@redis/json': 5.10.0(@redis/client@5.10.0) + '@redis/search': 5.10.0(@redis/client@5.10.0) + '@redis/time-series': 5.10.0(@redis/client@5.10.0) redux@5.0.1: {} @@ -5275,32 +5342,35 @@ snapshots: reusify@1.1.0: {} - rollup@4.53.3: + rollup@4.57.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.53.3 - '@rollup/rollup-android-arm64': 4.53.3 - '@rollup/rollup-darwin-arm64': 4.53.3 - '@rollup/rollup-darwin-x64': 4.53.3 - '@rollup/rollup-freebsd-arm64': 4.53.3 - '@rollup/rollup-freebsd-x64': 4.53.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 - '@rollup/rollup-linux-arm-musleabihf': 4.53.3 - '@rollup/rollup-linux-arm64-gnu': 4.53.3 - '@rollup/rollup-linux-arm64-musl': 4.53.3 - '@rollup/rollup-linux-loong64-gnu': 4.53.3 - '@rollup/rollup-linux-ppc64-gnu': 4.53.3 - '@rollup/rollup-linux-riscv64-gnu': 4.53.3 - '@rollup/rollup-linux-riscv64-musl': 4.53.3 - '@rollup/rollup-linux-s390x-gnu': 4.53.3 - '@rollup/rollup-linux-x64-gnu': 4.53.3 - '@rollup/rollup-linux-x64-musl': 4.53.3 - '@rollup/rollup-openharmony-arm64': 4.53.3 - '@rollup/rollup-win32-arm64-msvc': 4.53.3 - '@rollup/rollup-win32-ia32-msvc': 4.53.3 - '@rollup/rollup-win32-x64-gnu': 4.53.3 - '@rollup/rollup-win32-x64-msvc': 4.53.3 + '@rollup/rollup-android-arm-eabi': 4.57.0 + '@rollup/rollup-android-arm64': 4.57.0 + '@rollup/rollup-darwin-arm64': 4.57.0 + '@rollup/rollup-darwin-x64': 4.57.0 + '@rollup/rollup-freebsd-arm64': 4.57.0 + '@rollup/rollup-freebsd-x64': 4.57.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.0 + '@rollup/rollup-linux-arm-musleabihf': 4.57.0 + '@rollup/rollup-linux-arm64-gnu': 4.57.0 + '@rollup/rollup-linux-arm64-musl': 4.57.0 + '@rollup/rollup-linux-loong64-gnu': 4.57.0 + '@rollup/rollup-linux-loong64-musl': 4.57.0 + '@rollup/rollup-linux-ppc64-gnu': 4.57.0 + '@rollup/rollup-linux-ppc64-musl': 4.57.0 + '@rollup/rollup-linux-riscv64-gnu': 4.57.0 + '@rollup/rollup-linux-riscv64-musl': 4.57.0 + '@rollup/rollup-linux-s390x-gnu': 4.57.0 + '@rollup/rollup-linux-x64-gnu': 4.57.0 + '@rollup/rollup-linux-x64-musl': 4.57.0 + '@rollup/rollup-openbsd-x64': 4.57.0 + '@rollup/rollup-openharmony-arm64': 4.57.0 + '@rollup/rollup-win32-arm64-msvc': 4.57.0 + '@rollup/rollup-win32-ia32-msvc': 4.57.0 + '@rollup/rollup-win32-x64-gnu': 4.57.0 + '@rollup/rollup-win32-x64-msvc': 4.57.0 fsevents: 2.3.3 run-parallel@1.2.0: @@ -5326,7 +5396,7 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - satori@0.18.3: + satori@0.19.1: dependencies: '@shuding/opentype.js': 1.4.0-beta.0 css-background-parser: 0.1.0 @@ -5447,8 +5517,6 @@ snapshots: stackback@0.0.2: {} - standard-as-callback@2.1.0: {} - std-env@3.10.0: {} stop-iteration-iterator@1.1.0: @@ -5516,12 +5584,12 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 - styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.3): + styled-jsx@5.1.6(@babel/core@7.28.6)(react@19.2.4): dependencies: client-only: 0.0.1 - react: 19.2.3 + react: 19.2.4 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.28.6 supports-color@7.2.0: dependencies: @@ -5529,11 +5597,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swr@2.3.7(react@19.2.3): + swr@2.3.8(react@19.2.4): dependencies: dequal: 2.0.3 - react: 19.2.3 - use-sync-external-store: 1.6.0(react@19.2.3) + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) tailwindcss@4.1.18: {} @@ -5558,13 +5626,13 @@ snapshots: dependencies: is-number: 7.0.0 - token-types@6.1.1: + token-types@6.1.2: dependencies: - '@borewit/text-codec': 0.1.1 + '@borewit/text-codec': 0.2.1 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - ts-api-utils@2.1.0(typescript@5.9.3): + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -5614,12 +5682,12 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -5667,7 +5735,7 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.2.2(browserslist@4.28.1): + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 escalade: 3.2.0 @@ -5677,33 +5745,33 @@ snapshots: dependencies: punycode: 2.3.1 - use-sync-external-store@1.6.0(react@19.2.3): + use-sync-external-store@1.6.0(react@19.2.4): dependencies: - react: 19.2.3 + react: 19.2.4 - vite@7.2.7(@types/node@25.0.2)(jiti@2.6.1)(lightningcss@1.30.2): + vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2): dependencies: - esbuild: 0.25.12 + esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.53.3 + rollup: 4.57.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.0.2 + '@types/node': 25.1.0 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 - vitest@4.0.15(@types/node@25.0.2)(jiti@2.6.1)(lightningcss@1.30.2): + vitest@4.0.18(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2): dependencies: - '@vitest/expect': 4.0.15 - '@vitest/mocker': 4.0.15(vite@7.2.7(@types/node@25.0.2)(jiti@2.6.1)(lightningcss@1.30.2)) - '@vitest/pretty-format': 4.0.15 - '@vitest/runner': 4.0.15 - '@vitest/snapshot': 4.0.15 - '@vitest/spy': 4.0.15 - '@vitest/utils': 4.0.15 + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 es-module-lexer: 1.7.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -5715,10 +5783,10 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.2.7(@types/node@25.0.2)(jiti@2.6.1)(lightningcss@1.30.2) + vite: 7.3.1(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.0.2 + '@types/node': 25.1.0 transitivePeerDependencies: - jiti - less @@ -5754,7 +5822,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.19 + which-typed-array: 1.1.20 which-collection@1.0.2: dependencies: @@ -5763,7 +5831,7 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-typed-array@1.1.19: + which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.8 @@ -5790,8 +5858,8 @@ snapshots: yoga-layout@3.2.1: {} - zod-validation-error@4.0.2(zod@4.1.13): + zod-validation-error@4.0.2(zod@4.3.6): dependencies: - zod: 4.1.13 + zod: 4.3.6 - zod@4.1.13: {} + zod@4.3.6: {} diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts deleted file mode 100644 index 2e219a2..0000000 --- a/src/app/api/search/route.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { NextRequest } from "next/server"; - -import crypto from "crypto"; -import seedrandom from "seedrandom"; - -import { searchSchema } from "@/lib/schemas"; -import { RateLimit } from "@/lib/rate-limit"; -import { prisma } from "@/lib/prisma"; -import { Prisma } from "@prisma/client"; - -export async function GET(request: NextRequest) { - const rateLimit = new RateLimit(request, 24, "/api/search"); - const check = await rateLimit.handle(); - if (check) return check; - - const parsed = searchSchema.safeParse(Object.fromEntries(request.nextUrl.searchParams)); - if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400); - - const { q: query, sort, tags, gender, page = 1, limit = 24, seed } = parsed.data; - - const where: Prisma.MiiWhereInput = { - // Searching - ...(query && { - OR: [{ name: { contains: query, mode: "insensitive" } }, { tags: { has: query } }, { description: { contains: query, mode: "insensitive" } }], - }), - // Tag filtering - ...(tags && tags.length > 0 && { tags: { hasEvery: tags } }), - // Gender - ...(gender && { gender: { equals: gender } }), - }; - - const skip = (page - 1) * limit; - - if (sort === "random") { - // Use seed for consistent random results - const randomSeed = seed || crypto.randomInt(0, 1_000_000_000); - - // Get all IDs that match the where conditions - const matchingIds = await prisma.mii.findMany({ - where, - select: { id: true }, - }); - - if (matchingIds.length === 0) return rateLimit.sendResponse({ error: "No Miis found!" }, 404); - - const rng = seedrandom(randomSeed.toString()); - - // Randomize all IDs using the Durstenfeld algorithm - for (let i = matchingIds.length - 1; i > 0; i--) { - const j = Math.floor(rng() * (i + 1)); - [matchingIds[i], matchingIds[j]] = [matchingIds[j], matchingIds[i]]; - } - - // Convert to number[] array and return paginated results - return rateLimit.sendResponse(matchingIds.slice(skip, skip + limit).map((i) => i.id)); - } else { - // Sorting by likes, newest, or oldest - let orderBy: Prisma.MiiOrderByWithRelationInput[]; - - if (sort === "likes") { - orderBy = [{ likedBy: { _count: "desc" } }, { name: "asc" }]; - } else if (sort === "oldest") { - orderBy = [{ createdAt: "asc" }, { name: "asc" }]; - } else { - // default to newest - orderBy = [{ createdAt: "desc" }, { name: "asc" }]; - } - - const list = await prisma.mii.findMany({ - where, - orderBy, - select: { id: true }, - skip, - take: limit, - }); - - return rateLimit.sendResponse(list.map((mii) => mii.id)); - } -} diff --git a/src/app/api/submit/route.ts b/src/app/api/submit/route.ts index 561c762..44140b7 100644 --- a/src/app/api/submit/route.ts +++ b/src/app/api/submit/route.ts @@ -54,7 +54,7 @@ const submitSchema = z { message: "Gender and Mii portrait image are required for Switch platform", path: ["gender", "miiPortraitImage"], - } + }, ); export async function POST(request: NextRequest) { @@ -99,17 +99,28 @@ export async function POST(request: NextRequest) { }); if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400); - const data = parsed.data; + const { + platform, + name: uncensoredName, + tags: uncensoredTags, + description: uncensoredDescription, + qrBytesRaw, + gender, + miiPortraitImage, + image1, + image2, + image3, + } = parsed.data; // Censor potential inappropriate words - const name = profanity.censor(data.name); - const tags = data.tags.map((t) => profanity.censor(t)); - const description = data.description && profanity.censor(data.description); + const name = profanity.censor(uncensoredName); + const tags = uncensoredTags.map((t) => profanity.censor(t)); + const description = uncensoredDescription && profanity.censor(uncensoredDescription); // Validate image files const customImages: File[] = []; - for (const img of [data.image1, data.image2, data.image3]) { + for (const img of [image1, image2, image3]) { if (!img) continue; const imageValidation = await validateImage(img); @@ -121,16 +132,16 @@ export async function POST(request: NextRequest) { } // Check Mii portrait image as well (Switch) - if (data.platform === "SWITCH") { - const imageValidation = await validateImage(data.miiPortraitImage); + if (platform === "SWITCH") { + const imageValidation = await validateImage(miiPortraitImage); if (!imageValidation.valid) return rateLimit.sendResponse({ error: imageValidation.error }, imageValidation.status ?? 400); } - const qrBytes = new Uint8Array(data.qrBytesRaw); + const qrBytes = new Uint8Array(qrBytesRaw); // Convert QR code to JS (3DS) let conversion: { mii: Mii; tomodachiLifeMii: TomodachiLifeMii } | undefined; - if (data.platform === "THREE_DS") { + if (platform === "THREE_DS") { try { conversion = convertQrCode(qrBytes); } catch (error) { @@ -142,14 +153,14 @@ export async function POST(request: NextRequest) { const miiRecord = await prisma.mii.create({ data: { userId: Number(session.user.id), - platform: data.platform, + platform, name, tags, description, - gender: data.gender ?? "MALE", + gender: gender ?? "MALE", // Automatically detect certain information if on 3DS - ...(data.platform === "THREE_DS" && + ...(platform === "THREE_DS" && conversion && { firstName: conversion.tomodachiLifeMii.firstName, lastName: conversion.tomodachiLifeMii.lastName, @@ -168,7 +179,7 @@ export async function POST(request: NextRequest) { let portraitBuffer: Buffer | undefined; // Download the image of the Mii (3DS) - if (data.platform === "THREE_DS") { + if (platform === "THREE_DS") { const studioUrl = conversion?.mii.studioUrl({ width: 512 }); const studioResponse = await fetch(studioUrl!); @@ -177,8 +188,8 @@ export async function POST(request: NextRequest) { } portraitBuffer = Buffer.from(await studioResponse.arrayBuffer()); - } else if (data.platform === "SWITCH") { - portraitBuffer = Buffer.from(await data.miiPortraitImage.arrayBuffer()); + } else if (platform === "SWITCH") { + portraitBuffer = Buffer.from(await miiPortraitImage.arrayBuffer()); } if (!portraitBuffer) throw Error("Mii portrait buffer not initialised"); @@ -215,6 +226,8 @@ export async function POST(request: NextRequest) { // Clean up if something went wrong await prisma.mii.delete({ where: { id: miiRecord.id } }); + console.error("Error processing Mii files:", error); + return rateLimit.sendResponse({ error: "Failed to process and store Mii files" }, 500); console.error("Error generating QR code:", error); return rateLimit.sendResponse({ error: "Failed to generate QR code" }, 500); } @@ -227,7 +240,7 @@ export async function POST(request: NextRequest) { { error: `Failed to generate 'metadata' type image for mii ${miiRecord.id}`, }, - 500 + 500, ); } @@ -240,7 +253,7 @@ export async function POST(request: NextRequest) { const fileLocation = path.join(miiUploadsDirectory, `image${index}.webp`); await fs.writeFile(fileLocation, webpBuffer); - }) + }), ); // Update database to tell it how many images exist diff --git a/src/app/globals.css b/src/app/globals.css index bcf27ad..83b143c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -25,9 +25,12 @@ body { --color1: var(--color-amber-50); --color2: var(--color-amber-100); - background-image: repeating-linear-gradient(45deg, var(--color1) 25%, transparent 25%, transparent 75%, var(--color1) 75%, var(--color1)), + background-image: + repeating-linear-gradient(45deg, var(--color1) 25%, transparent 25%, transparent 75%, var(--color1) 75%, var(--color1)), repeating-linear-gradient(45deg, var(--color1) 25%, var(--color2) 25%, var(--color2) 75%, var(--color1) 75%, var(--color1)); - background-position: 0 0, 10px 10px; + background-position: + 0 0, + 10px 10px; background-size: 20px 20px; } @@ -41,14 +44,17 @@ body { .button:disabled { @apply text-zinc-600 bg-zinc-100! border-zinc-300! cursor-auto; + @apply text-zinc-600 bg-zinc-100! border-zinc-300! cursor-auto; } .input { @apply bg-orange-200! outline-0 focus:ring-[3px] ring-orange-400/50 transition placeholder:text-black/40; + @apply bg-orange-200! outline-0 focus:ring-[3px] ring-orange-400/50 transition placeholder:text-black/40; } .input:disabled { @apply text-zinc-600 bg-zinc-100! border-zinc-300!; + @apply text-zinc-600 bg-zinc-100! border-zinc-300!; } .checkbox { @@ -64,7 +70,13 @@ body { @apply block; } -/* Tooltips */ +.checkbox-alt { + @apply relative appearance-none bg-zinc-400 rounded-2xl h-5 w-8.5 cursor-pointer transition-all + after:transition-all after:bg-zinc-100 after:rounded-full after:h-3.5 after:absolute after:w-3.5 + after:left-[3px] after:top-[3px] hover:bg-zinc-500 checked:bg-orange-400 checked:after:left-[16px] + checked:hover:bg-orange-500 ml-auto; +} + [data-tooltip] { @apply relative z-10; } @@ -82,23 +94,6 @@ body { @apply opacity-100 scale-100; } -/* Fallback Tooltips */ -[data-tooltip-span] { - @apply relative; -} - -[data-tooltip-span] > .tooltip { - @apply absolute left-1/2 top-full mt-2 px-2 py-1 bg-orange-400 border border-orange-400 rounded-md text-sm text-white whitespace-nowrap select-none pointer-events-none shadow-md opacity-0 scale-75 transition-all duration-200 ease-out origin-top -translate-x-1/2 z-999999; -} - -[data-tooltip-span] > .tooltip::before { - @apply content-[''] absolute left-1/2 -translate-x-1/2 -top-2 border-4 border-transparent border-b-orange-400; -} - -[data-tooltip-span]:hover > .tooltip { - @apply opacity-100 scale-100; -} - /* Scrollbars */ /* Firefox */ * { diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index ac3924e..e848d7e 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -28,6 +28,18 @@ export default async function LoginPage() { + +

+ By signing up, you agree to the{" "} + + Terms of Service + {" "} + and{" "} + + Privacy Policy + + . +

); diff --git a/src/app/mii/[id]/data/route.ts b/src/app/mii/[id]/data/route.ts deleted file mode 100644 index 4f4134d..0000000 --- a/src/app/mii/[id]/data/route.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { NextRequest } from "next/server"; - -import { idSchema } from "@/lib/schemas"; -import { RateLimit } from "@/lib/rate-limit"; -import { prisma } from "@/lib/prisma"; - -export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const rateLimit = new RateLimit(request, 3, "/mii/data"); - const check = await rateLimit.handle(); - if (check) return check; - - const { id: slugId } = await params; - const parsed = idSchema.safeParse(slugId); - if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400); - const miiId = parsed.data; - - const data = await prisma.mii.findUnique({ - where: { id: miiId }, - select: { - id: true, - name: true, - _count: { - select: { - likedBy: true, - }, - }, - platform: true, - imageCount: true, - tags: true, - description: true, - firstName: true, - lastName: true, - gender: true, - islandName: true, - allowedCopying: true, - createdAt: true, - user: { select: { id: true, username: true, name: true } }, - }, - }); - - if (!data) { - return rateLimit.sendResponse({ error: "Mii not found" }, 404); - } - - const { _count, ...rest } = data; - return rateLimit.sendResponse({ - ...rest, - likes: _count.likedBy, - }); -} diff --git a/src/app/mii/[id]/page.tsx b/src/app/mii/[id]/page.tsx index 4acf6ea..ac7044c 100644 --- a/src/app/mii/[id]/page.tsx +++ b/src/app/mii/[id]/page.tsx @@ -103,7 +103,7 @@ export default async function MiiPage({ params }: Props) { userId: Number(session.user.id), }, select: { userId: true }, - } + } : false, _count: { select: { likedBy: true }, // Get total like count @@ -235,6 +235,7 @@ export default async function MiiPage({ params }: Props) {

{mii.name}

{/* Like button */} 0} isLoggedIn={session?.user != null} big /> + 0} isLoggedIn={session?.user != null} big /> {/* Tags */}
diff --git a/src/app/page.tsx b/src/app/page.tsx index 215e31c..801e5e7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,10 +1,12 @@ import { Metadata } from "next"; import { redirect } from "next/navigation"; import { Suspense } from "react"; +import { Icon } from "@iconify/react"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; +import Countdown from "@/components/countdown"; import MiiList from "@/components/mii-list"; import Skeleton from "@/components/mii-list/skeleton"; @@ -35,7 +37,7 @@ export async function generateMetadata({ searchParams }: Props): Promise

{tags ? `Miis tagged with '${tags}' - TomodachiShare` : "TomodachiShare - index mii list"}

+ {(!page || page === "1") && ( +
+ + +
+

Join the Discord

+

Code: 48cXBFKvWQ

+
+
+ +
+ )} }> diff --git a/src/app/submit/page.tsx b/src/app/submit/page.tsx index 0cd7a47..f2c63dd 100644 --- a/src/app/submit/page.tsx +++ b/src/app/submit/page.tsx @@ -32,8 +32,13 @@ export default async function SubmitPage() { if (activePunishment) redirect("/off-the-island"); // Check if submissions are disabled - const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`); - const { value } = await response.json(); + let value: boolean | null = true; + try { + const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`); + value = await response.json(); + } catch (error) { + return

An error occurred!

; + } if (!value) return ( diff --git a/src/components/countdown.tsx b/src/components/countdown.tsx new file mode 100644 index 0000000..a6d5caa --- /dev/null +++ b/src/components/countdown.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export default function Countdown() { + const [days, setDays] = useState(31); + const [hours, setHours] = useState(59); + const [minutes, setMinutes] = useState(59); + const [seconds, setSeconds] = useState(59); + + const targetDate = new Date("2026-04-16T00:00:00Z").getTime(); + + useEffect(() => { + const interval = setInterval(() => { + const now = new Date().getTime(); + const distance = targetDate - now; + + if (distance < 0) { + clearInterval(interval); + setDays(0); + setHours(0); + setMinutes(0); + setSeconds(0); + return; + } + + setDays(Math.floor(distance / (1000 * 60 * 60 * 24))); + setHours(Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))); + setMinutes(Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60))); + setSeconds(Math.floor((distance % (1000 * 60)) / 1000)); + }, 100); + + return () => clearInterval(interval); + }, []); + + return ( +
+
+

Living the Dream

+

releases in:

+
+ +
+
+ {days} + days +
+
+ {hours} + hours +
+
+ {minutes} + minutes +
+
+ {seconds} + seconds +
+
+
+ ); +} diff --git a/src/components/image-viewer.tsx b/src/components/image-viewer.tsx index dfa62cc..eb8ec28 100644 --- a/src/components/image-viewer.tsx +++ b/src/components/image-viewer.tsx @@ -20,7 +20,7 @@ export default function ImageViewer({ src, alt, width, height, className, images const [isOpen, setIsOpen] = useState(false); const [isVisible, setIsVisible] = useState(false); - const [emblaRef, emblaApi] = useEmblaCarousel(); + const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, duration: 15 }); const [selectedIndex, setSelectedIndex] = useState(0); const [scrollSnaps, setScrollSnaps] = useState([]); @@ -44,7 +44,7 @@ export default function ImageViewer({ src, alt, width, height, className, images // Keep order of images whilst opening at src prop const index = images.indexOf(src); if (index !== -1) { - emblaApi.scrollTo(index); + emblaApi.scrollTo(index, true); setSelectedIndex(index); } @@ -80,83 +80,74 @@ export default function ImageViewer({ src, alt, width, height, className, images
-
-
- -
+ + -
-
- {imagesMap.map((image, index) => ( -
- {alt} -
- ))} -
+
+
+ {imagesMap.map((image, index) => ( +
+ {alt} +
+ ))}
- {images.length != 0 && ( + {images.length > 1 && ( <> + {/* Carousel counter */} +
+ {selectedIndex + 1} / {images.length} +
+ {/* Carousel buttons */} {/* Prev button */} -
emblaApi?.scrollPrev()} + className={`absolute left-2 top-1/2 -translate-y-1/2 pill button p-0.5! aspect-square text-4xl transition-opacity duration-300 ${isVisible ? "opacity-100" : "opacity-0"}`} > - -
+ + {/* Next button */} -
emblaApi?.scrollNext()} + className={`absolute right-2 top-1/2 -translate-y-1/2 pill button p-0.5! aspect-square text-4xl transition-opacity duration-300 ${isVisible ? "opacity-100" : "opacity-0"}`} > - -
+ + {/* Carousel snaps */}
@@ -165,14 +156,14 @@ export default function ImageViewer({ src, alt, width, height, className, images key={index} aria-label={`Go to ${index} in Carousel`} onClick={() => emblaApi?.scrollTo(index)} - className={`size-2.5 cursor-pointer rounded-full ${index === selectedIndex ? "bg-black" : "bg-black/25"}`} + className={`size-2 cursor-pointer rounded-full transition-all duration-300 ${index === selectedIndex ? "bg-orange-300 w-8" : "bg-orange-300/40"}`} /> ))}
)}
, - document.body + document.body, )} ); diff --git a/src/components/like-button.tsx b/src/components/like-button.tsx index 9fe8070..2152553 100644 --- a/src/components/like-button.tsx +++ b/src/components/like-button.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import { redirect } from "next/navigation"; +import { useRouter } from "next/navigation"; import { Icon, loadIcons } from "@iconify/react"; import { abbreviateNumber } from "@/lib/abbreviation"; @@ -16,13 +16,18 @@ interface Props { } export default function LikeButton({ likes, isLiked, miiId, isLoggedIn, disabled, abbreviate, big }: Props) { + const router = useRouter(); + const [isLikedState, setIsLikedState] = useState(isLiked); const [likesState, setLikesState] = useState(likes); const [isAnimating, setIsAnimating] = useState(false); const onClick = async () => { if (disabled) return; - if (!isLoggedIn) redirect("/login"); + if (!isLoggedIn) { + router.push("/login"); + return; + } setIsLikedState(!isLikedState); setLikesState(isLikedState ? likesState - 1 : likesState + 1); diff --git a/src/components/mii-list/filter-menu.tsx b/src/components/mii-list/filter-menu.tsx index 0064883..848e607 100644 --- a/src/components/mii-list/filter-menu.tsx +++ b/src/components/mii-list/filter-menu.tsx @@ -4,11 +4,11 @@ import { useSearchParams } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import { Icon } from "@iconify/react"; -import { MiiGender, MiiPlatform } from "@prisma/client"; +import { MiiGender } from "@prisma/client"; import TagFilter from "./tag-filter"; -import PlatformSelect from "./platform-select"; import GenderSelect from "./gender-select"; +import OtherFilters from "./other-filters"; export default function FilterMenu() { const searchParams = useSearchParams(); @@ -17,8 +17,9 @@ export default function FilterMenu() { const [isVisible, setIsVisible] = useState(false); const rawTags = searchParams.get("tags") || ""; - const platform = (searchParams.get("platform") as MiiPlatform) || undefined; + const rawExclude = searchParams.get("exclude") || ""; const gender = (searchParams.get("gender") as MiiGender) || undefined; + const allowCopying = (searchParams.get("allowCopying") as unknown as boolean) || false; const tags = useMemo( () => @@ -28,7 +29,17 @@ export default function FilterMenu() { .map((tag) => tag.trim()) .filter((tag) => tag.length > 0) : [], - [rawTags] + [rawTags], + ); + const exclude = useMemo( + () => + rawExclude + ? rawExclude + .split(",") + .map((tag) => tag.trim()) + .filter((tag) => tag.length > 0) + : [], + [rawExclude], ); const [filterCount, setFilterCount] = useState(tags.length); @@ -49,45 +60,56 @@ export default function FilterMenu() { // Count all active filters useEffect(() => { - let count = tags.length; - if (platform) count++; + let count = tags.length + exclude.length; if (gender) count++; + if (allowCopying) count++; setFilterCount(count); - }, [tags, platform, gender]); + }, [tags, exclude, gender, allowCopying]); return (
{isOpen && (
{/* Arrow */}
+
+
+ Tags Include +
+
-
-
- Platform -
+
+
+ Tags Exclude +
- +
-
+
Gender -
+
+ +
+
+ Other +
+
+
)}
diff --git a/src/components/mii-list/gender-select.tsx b/src/components/mii-list/gender-select.tsx index 41ddd00..147c645 100644 --- a/src/components/mii-list/gender-select.tsx +++ b/src/components/mii-list/gender-select.tsx @@ -28,7 +28,7 @@ export default function GenderSelect() { } startTransition(() => { - router.push(`?${params.toString()}`); + router.push(`?${params.toString()}`, { scroll: false }); }); }; diff --git a/src/components/mii-list/index.tsx b/src/components/mii-list/index.tsx index 15259c8..3e3edba 100644 --- a/src/components/mii-list/index.tsx +++ b/src/components/mii-list/index.tsx @@ -10,12 +10,12 @@ import { searchSchema } from "@/lib/schemas"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; -import FilterMenu from "./filter-menu"; import SortSelect from "./sort-select"; import Carousel from "../carousel"; import LikeButton from "../like-button"; import DeleteMiiButton from "../delete-mii"; import Pagination from "./pagination"; +import FilterMenu from "./filter-menu"; interface Props { searchParams: { [key: string]: string | string[] | undefined }; @@ -23,26 +23,13 @@ interface Props { inLikesPage?: boolean; // Self-explanatory } -export default async function MiiList({ - searchParams, - userId, - inLikesPage, -}: Props) { +export default async function MiiList({ searchParams, userId, inLikesPage }: Props) { const session = await auth(); const parsed = searchSchema.safeParse(searchParams); if (!parsed.success) return

{parsed.error.issues[0].message}

; - const { - q: query, - sort, - tags, - platform, - gender, - page = 1, - limit = 24, - seed, - } = parsed.data; + const { q: query, sort, tags, exclude, platform, gender, allowCopying, page = 1, limit = 24, seed } = parsed.data; // My Likes page let miiIdsLiked: number[] | undefined = undefined; @@ -60,18 +47,17 @@ export default async function MiiList({ ...(inLikesPage && miiIdsLiked && { id: { in: miiIdsLiked } }), // Searching ...(query && { - OR: [ - { name: { contains: query, mode: "insensitive" } }, - { tags: { has: query } }, - { description: { contains: query, mode: "insensitive" } }, - ], + OR: [{ name: { contains: query, mode: "insensitive" } }, { tags: { has: query } }, { description: { contains: query, mode: "insensitive" } }], }), // Tag filtering ...(tags && tags.length > 0 && { tags: { hasEvery: tags } }), + ...(exclude && exclude.length > 0 && { NOT: { tags: { hasSome: exclude } } }), // Platform ...(platform && { platform: { equals: platform } }), // Gender ...(gender && { gender: { equals: gender } }), + // Allow Copying + ...(allowCopying && { allowedCopying: true }), // Profiles ...(userId && { userId }), }; @@ -93,6 +79,7 @@ export default async function MiiList({ tags: true, createdAt: true, gender: true, + allowedCopying: true, // Mii liked check ...(session?.user?.id && { likedBy: { @@ -113,9 +100,6 @@ export default async function MiiList({ let list: Prisma.MiiGetPayload<{ select: typeof select }>[]; if (sort === "random") { - // Use seed for consistent random results - const randomSeed = seed || crypto.randomInt(0, 1_000_000_000); - // Get all IDs that match the where conditions const matchingIds = await prisma.mii.findMany({ where, @@ -123,10 +107,12 @@ export default async function MiiList({ }); totalCount = matchingIds.length; - filteredCount = Math.min(matchingIds.length, limit); + filteredCount = Math.max(0, Math.min(limit, totalCount - skip)); if (matchingIds.length === 0) return; + // Use seed for consistent random results + const randomSeed = seed || crypto.randomInt(0, 1_000_000_000); const rng = seedrandom(randomSeed.toString()); // Randomize all IDs using the Durstenfeld algorithm @@ -136,7 +122,7 @@ export default async function MiiList({ } // Convert to number[] array - const selectedIds = matchingIds.slice(0, limit).map((i) => i.id); + const selectedIds = matchingIds.slice(skip, skip + limit).map((i) => i.id); list = await prisma.mii.findMany({ where: { @@ -183,22 +169,14 @@ export default async function MiiList({
{totalCount == filteredCount ? ( <> - - {totalCount} - - - {totalCount === 1 ? "Mii" : "Miis"} - + {totalCount} + {totalCount === 1 ? "Mii" : "Miis"} ) : ( <> - - {filteredCount} - + {filteredCount} of - - {totalCount} - + {totalCount} Miis )} @@ -220,66 +198,37 @@ export default async function MiiList({ images={[ `/mii/${mii.id}/image?type=mii`, `/mii/${mii.id}/image?type=qr-code`, - ...Array.from( - { length: mii.imageCount }, - (_, index) => `/mii/${mii.id}/image?type=image${index}` - ), + ...Array.from({ length: mii.imageCount }, (_, index) => `/mii/${mii.id}/image?type=image${index}`), ]} />
- + {mii.name}
{mii.tags.map((tag) => ( - + {tag} ))}
- + {!userId && ( - + @{mii.user?.username} )} {userId && Number(session?.user.id) == userId && (
- + - +
)}
diff --git a/src/components/mii-list/other-filters.tsx b/src/components/mii-list/other-filters.tsx new file mode 100644 index 0000000..8a2a346 --- /dev/null +++ b/src/components/mii-list/other-filters.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useRouter, useSearchParams } from "next/navigation"; +import React, { ChangeEvent, ChangeEventHandler, useState, useTransition } from "react"; + +export default function OtherFilters() { + const router = useRouter(); + const searchParams = useSearchParams(); + const [, startTransition] = useTransition(); + + const [allowCopying, setAllowCopying] = useState((searchParams.get("allowCopying") as unknown as boolean) ?? false); + + const handleChangeAllowCopying = (e: ChangeEvent) => { + setAllowCopying(e.target.checked); + + const params = new URLSearchParams(searchParams); + params.set("page", "1"); + + if (!allowCopying) { + params.set("allowCopying", "true"); + } else { + params.delete("allowCopying"); + } + + startTransition(() => { + router.push(`?${params.toString()}`, { scroll: false }); + }); + }; + + return ( +
+ + +
+ ); +} diff --git a/src/components/mii-list/sort-select.tsx b/src/components/mii-list/sort-select.tsx index 233295c..e62ed01 100644 --- a/src/components/mii-list/sort-select.tsx +++ b/src/components/mii-list/sort-select.tsx @@ -31,7 +31,7 @@ export default function SortSelect() { } startTransition(() => { - router.push(`?${params.toString()}`); + router.push(`?${params.toString()}`, { scroll: false }); }); }, }); @@ -54,11 +54,7 @@ export default function SortSelect() { > {isOpen && items.map((item, index) => ( -
  • +
  • {item}
  • ))} diff --git a/src/components/mii-list/tag-filter.tsx b/src/components/mii-list/tag-filter.tsx index 7607a0b..4cbbb8c 100644 --- a/src/components/mii-list/tag-filter.tsx +++ b/src/components/mii-list/tag-filter.tsx @@ -4,12 +4,16 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useMemo, useState, useTransition } from "react"; import TagSelector from "../tag-selector"; -export default function TagFilter() { +interface Props { + isExclude?: boolean; +} + +export default function TagFilter({ isExclude }: Props) { const router = useRouter(); const searchParams = useSearchParams(); const [, startTransition] = useTransition(); - const rawTags = searchParams.get("tags") || ""; + const rawTags = searchParams.get(isExclude ? "exclude" : "tags") || ""; const preexistingTags = useMemo( () => rawTags @@ -18,7 +22,7 @@ export default function TagFilter() { .map((tag) => tag.trim()) .filter((tag) => tag.length > 0) : [], - [rawTags] + [rawTags], ); const [tags, setTags] = useState(preexistingTags); @@ -39,20 +43,20 @@ export default function TagFilter() { params.set("page", "1"); if (tags.length > 0) { - params.set("tags", stateTags); + params.set(isExclude ? "exclude" : "tags", stateTags); } else { - params.delete("tags"); + params.delete(isExclude ? "exclude" : "tags"); } startTransition(() => { - router.push(`?${params.toString()}`); + router.push(`?${params.toString()}`, { scroll: false }); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [tags]); return (
    - +
    ); } diff --git a/src/components/search-bar.tsx b/src/components/search-bar.tsx index d25cafe..044a0a1 100644 --- a/src/components/search-bar.tsx +++ b/src/components/search-bar.tsx @@ -1,24 +1,28 @@ "use client"; -import { redirect, useSearchParams } from "next/navigation"; +import { redirect, useRouter, useSearchParams } from "next/navigation"; import { useState } from "react"; import { Icon } from "@iconify/react"; import { querySchema } from "@/lib/schemas"; export default function SearchBar() { + const router = useRouter(); const searchParams = useSearchParams(); const [query, setQuery] = useState(""); const handleSearch = () => { const result = querySchema.safeParse(query); - if (!result.success) redirect("/"); + if (!result.success) { + router.push("/", { scroll: false }); + return; + } // Clone current search params and add query param const params = new URLSearchParams(searchParams.toString()); params.set("q", query); params.set("page", "1"); - redirect(`/?${params.toString()}`); + router.push(`/?${params.toString()}`, { scroll: false }); }; const handleKeyDown = (event: React.KeyboardEvent) => { diff --git a/src/components/submit-form/index.tsx b/src/components/submit-form/index.tsx index 40a3feb..2b46938 100644 --- a/src/components/submit-form/index.tsx +++ b/src/components/submit-form/index.tsx @@ -34,7 +34,7 @@ export default function SubmitForm() { if (files.length >= 3) return; setFiles((prev) => [...prev, ...acceptedFiles]); }, - [files.length] + [files.length], ); const [isQrScannerOpen, setIsQrScannerOpen] = useState(false); @@ -101,7 +101,7 @@ export default function SubmitForm() { const { id, error } = await response.json(); if (!response.ok) { - setError(error); + setError(String(error)); // app can crash if error message is not a string return; } diff --git a/src/components/submit-form/qr-scanner.tsx b/src/components/submit-form/qr-scanner.tsx index 14bc1e4..e125253 100644 --- a/src/components/submit-form/qr-scanner.tsx +++ b/src/components/submit-form/qr-scanner.tsx @@ -1,7 +1,6 @@ "use client"; import { useCallback, useEffect, useRef, useState } from "react"; -import Webcam from "react-webcam"; import jsQR from "jsqr"; import { Icon } from "@iconify/react"; @@ -17,14 +16,12 @@ interface Props { export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) { const [isVisible, setIsVisible] = useState(false); - const [permissionGranted, setPermissionGranted] = useState( - null - ); + const [permissionGranted, setPermissionGranted] = useState(null); const [devices, setDevices] = useState([]); const [selectedDeviceId, setSelectedDeviceId] = useState(null); - const webcamRef = useRef(null); + const videoRef = useRef(null); const requestRef = useRef(null); const canvasRef = useRef(null); @@ -42,8 +39,7 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) { selectedItem, } = useSelect({ items: cameraItems, - selectedItem: - cameraItems.find((item) => item.value === selectedDeviceId) ?? null, + selectedItem: cameraItems.find((item) => item.value === selectedDeviceId) ?? null, onSelectedItemChange: ({ selectedItem }) => { setSelectedDeviceId(selectedItem?.value ?? null); }, @@ -55,12 +51,9 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) { // Continue scanning in a loop requestRef.current = requestAnimationFrame(scanQRCode); - const webcam = webcamRef.current; + const video = videoRef.current; const canvas = canvasRef.current; - if (!webcam || !canvas) return; - - const video = webcam.video; - if (!video || video.videoWidth === 0 || video.videoHeight === 0) return; + if (!video || video.videoWidth === 0 || video.videoHeight === 0 || !canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; @@ -69,14 +62,9 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) { canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); - const imageData = ctx.getImageData( - 0, - 0, - video.videoWidth, - video.videoHeight - ); + const imageData = ctx.getImageData(0, 0, video.videoWidth, video.videoHeight); const code = jsQR(imageData.data, imageData.width, imageData.height); - if (!code) return; + if (!code || !code.binaryData) return; // Cancel animation frame to stop scanning if (requestRef.current) { @@ -84,15 +72,20 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) { requestRef.current = null; } - setQrBytesRaw(code.binaryData!); + setQrBytesRaw(code.binaryData); setIsOpen(false); }, [isOpen, setIsOpen, setQrBytesRaw]); - const requestPermission = async () => { + const requestPermission = () => { + if (!navigator.mediaDevices) return; + navigator.mediaDevices - .getUserMedia({ video: true }) + .getUserMedia({ video: true, audio: false }) .then(() => setPermissionGranted(true)) - .catch(() => setPermissionGranted(false)); + .catch((err) => { + setPermissionGranted(false); + console.error("An error occurred trying to access the camera", err); + }); }; const close = () => { @@ -106,34 +99,50 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) { if (isOpen) { // slight delay to trigger animation setTimeout(() => setIsVisible(true), 10); + requestPermission(); } }, [isOpen]); - useEffect(() => { - if (!isOpen) return; - requestPermission(); - - if (!navigator.mediaDevices.enumerateDevices) return; - navigator.mediaDevices.enumerateDevices().then((devices) => { - const videoDevices = devices.filter((d) => d.kind === "videoinput"); - setDevices(videoDevices); - if (!selectedDeviceId && videoDevices.length > 0) { - setSelectedDeviceId(videoDevices[0].deviceId); - } - }); - }, [isOpen, selectedDeviceId]); - useEffect(() => { if (!isOpen || !permissionGranted) return; + navigator.mediaDevices + .enumerateDevices() + .then((devices) => { + const videoDevices = devices.filter((d) => d.kind === "videoinput"); + setDevices(videoDevices); + + const targetDeviceId = selectedDeviceId || videoDevices[0]?.deviceId; + if (!targetDeviceId) return; + setSelectedDeviceId(targetDeviceId); + + // start camera stream + return navigator.mediaDevices.getUserMedia({ + video: { deviceId: targetDeviceId }, + audio: false, + }); + }) + .then((stream) => { + if (!stream || !videoRef.current) return; + videoRef.current.srcObject = stream; + videoRef.current.play(); + }) + .catch((err) => console.error("Camera error", err)); + requestRef.current = requestAnimationFrame(scanQRCode); + // cleanup return () => { if (requestRef.current) { cancelAnimationFrame(requestRef.current); } + if (videoRef.current?.srcObject) { + const stream = videoRef.current.srcObject as MediaStream; + stream.getTracks().forEach((track) => track.stop()); + videoRef.current.srcObject = null; + } }; - }, [isOpen, permissionGranted, scanQRCode]); + }, [isOpen, permissionGranted, selectedDeviceId, scanQRCode]); if (!isOpen) return null; @@ -141,9 +150,7 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) {

    Scan QR Code

    -
    @@ -191,9 +193,7 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) {
  • {item.label}
  • @@ -204,51 +204,19 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) { )}
    - {!permissionGranted ? ( -
    -

    - Camera access denied -

    -

    - Please allow camera access in your browser settings to scan QR - codes -

    -
    - ) : ( - <> - { - const newDevices = - await navigator.mediaDevices.enumerateDevices(); - const videoDevices = newDevices.filter( - (d) => d.kind === "videoinput" - ); - setDevices(videoDevices); - }} - className="size-full object-cover rounded-2xl border-2 border-amber-500" - /> - - - )} + +
    diff --git a/src/components/tag-selector.tsx b/src/components/tag-selector.tsx index 437ffc2..a8e848a 100644 --- a/src/components/tag-selector.tsx +++ b/src/components/tag-selector.tsx @@ -8,32 +8,18 @@ interface Props { tags: string[]; setTags: React.Dispatch>; showTagLimit?: boolean; + isExclude?: boolean; } const tagRegex = /^[a-z0-9-_]*$/; -const predefinedTags = [ - "anime", - "art", - "cartoon", - "celebrity", - "games", - "history", - "meme", - "movie", - "oc", - "tv", -]; +const predefinedTags = ["anime", "art", "cartoon", "celebrity", "games", "history", "meme", "movie", "oc", "tv"]; -export default function TagSelector({ tags, setTags, showTagLimit }: Props) { +export default function TagSelector({ tags, setTags, showTagLimit, isExclude }: Props) { const [inputValue, setInputValue] = useState(""); const inputRef = useRef(null); const getFilteredItems = (): string[] => - predefinedTags - .filter((item) => - item.toLowerCase().includes(inputValue?.toLowerCase() || "") - ) - .filter((item) => !tags.includes(item)); + predefinedTags.filter((item) => item.toLowerCase().includes(inputValue?.toLowerCase() || "")).filter((item) => !tags.includes(item)); const filteredItems = getFilteredItems(); const isMaxItemsSelected = tags.length >= 8; @@ -49,37 +35,39 @@ export default function TagSelector({ tags, setTags, showTagLimit }: Props) { setTags(tags.filter((t) => t !== tag)); }; - const { - isOpen, - openMenu, - getToggleButtonProps, - getMenuProps, - getInputProps, - getItemProps, - highlightedIndex, - } = useCombobox({ + const { isOpen, openMenu, getToggleButtonProps, getMenuProps, getInputProps, getItemProps, highlightedIndex } = useCombobox({ inputValue, items: filteredItems, + selectedItem: null, onInputValueChange: ({ inputValue }) => { - if (inputValue && !tagRegex.test(inputValue)) return; - setInputValue(inputValue || ""); + const newValue = inputValue || ""; + if (newValue && !tagRegex.test(newValue)) return; + setInputValue(newValue); }, - onStateChange: ({ type, selectedItem }) => { + onSelectedItemChange: ({ type, selectedItem }) => { if (type === useCombobox.stateChangeTypes.ItemClick && selectedItem) { addTag(selectedItem); setInputValue(""); } }, + stateReducer: (_, { type, changes }) => { + // Prevent input from being filled when item is selected + if (type === useCombobox.stateChangeTypes.ItemClick) { + return { + ...changes, + inputValue: "", + }; + } + return changes; + }, }); const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === "Enter" && inputValue && !tags.includes(inputValue)) { addTag(inputValue); setInputValue(""); - } - - // Spill onto last tag - if (event.key === "Backspace" && inputValue === "") { + } else if (event.key === "Backspace" && inputValue === "") { + // Spill onto last tag const lastTag = tags[tags.length - 1]; setInputValue(lastTag); removeTag(lastTag); @@ -104,10 +92,7 @@ export default function TagSelector({ tags, setTags, showTagLimit }: Props) { {/* Tags */}
    {tags.map((tag) => ( - + {tag} )} @@ -176,9 +153,7 @@ export default function TagSelector({ tags, setTags, showTagLimit }: Props) {
  • {item}
  • @@ -202,9 +177,7 @@ export default function TagSelector({ tags, setTags, showTagLimit }: Props) { {showTagLimit && (
    {isMaxItemsSelected ? ( - - Maximum of 8 tags reached. Remove a tag to add more. - + Maximum of 8 tags reached. Remove a tag to add more. ) : ( {tags.length}/8 tags )} diff --git a/src/lib/mii.js/extended-bit-stream.ts b/src/lib/mii.js/extended-bit-stream.ts index 9eeb74d..e01188c 100644 --- a/src/lib/mii.js/extended-bit-stream.ts +++ b/src/lib/mii.js/extended-bit-stream.ts @@ -1,4 +1,5 @@ -// Stolen from https://github.com/PretendoNetwork/mii-js/ +// Based on https://github.com/PretendoNetwork/mii-js/ +// Updated to bit-buffer v0.3.0 import { BitStream } from "bit-buffer"; @@ -11,78 +12,34 @@ export default class ExtendedBitStream extends BitStream { this.bigEndian = !this.bigEndian; } - // the type definition for BitStream does not include the _index property - // since it's supposed to be private, but it's needed 4 times here sooo - public alignByte(): void { - // @ts-expect-error _index is private - const nextClosestByteIndex = 8 * Math.ceil(this._index / 8); - // @ts-expect-error _index is private - const bitDistance = nextClosestByteIndex - this._index; - + const nextClosestByteIndex = 8 * Math.ceil(this.index / 8); + const bitDistance = nextClosestByteIndex - this.index; this.skipBits(bitDistance); } - public bitSeek(bitPos: number): void { - // @ts-expect-error _index is private - this._index = bitPos; - } - public skipBits(bitCount: number): void { - // @ts-expect-error _index is private - this._index += bitCount; - } - - public skipBytes(bytes: number): void { - const bits = bytes * 8; - this.skipBits(bits); + this.index += bitCount; } public skipBit(): void { this.skipBits(1); } - public skipInt8(): void { - this.skipBytes(1); - } - public skipInt16(): void { // Skipping a uint16 is the same as skipping 2 uint8's - this.skipBytes(2); + this.skipBits(16); } public readBit(): number { - return this.readBits(1); - } - - public readBytes(length: number): number[] { - return Array(length) - .fill(0) - .map(() => this.readUint8()); + return this.readBits(1, false); } public readBuffer(length: number): Buffer { - return Buffer.from(this.readBytes(length)); + return Buffer.from(super.readBytes(length)); } public readUTF16String(length: number): string { return this.readBuffer(length).toString("utf16le").replace(/\0.*$/, ""); } - - public writeBit(bit: number): void { - this.writeBits(bit, 1); - } - - public writeBuffer(buffer: Buffer): void { - buffer.forEach((byte) => this.writeUint8(byte)); - } - - public writeUTF16String(string: string): void { - const stringBuffer = Buffer.from(string, "utf16le"); - const terminatedBuffer = Buffer.alloc(0x14); - - stringBuffer.copy(terminatedBuffer); - - this.writeBuffer(terminatedBuffer); - } } diff --git a/src/lib/mii.js/mii.ts b/src/lib/mii.js/mii.ts index fe7cd94..799babb 100644 --- a/src/lib/mii.js/mii.ts +++ b/src/lib/mii.js/mii.ts @@ -66,7 +66,14 @@ const STUDIO_RENDER_CLOTHES_COLORS = [ "black", ]; -const STUDIO_RENDER_LIGHT_DIRECTION_MODS = ["none", "zerox", "flipx", "camera", "offset", "set"]; +const STUDIO_RENDER_LIGHT_DIRECTION_MODS = [ + "none", + "zerox", + "flipx", + "camera", + "offset", + "set", +]; const STUDIO_RENDER_INSTANCE_ROTATION_MODES = ["model", "camera", "both"]; @@ -74,6 +81,7 @@ const STUDIO_BG_COLOR_REGEX = /^[0-9A-F]{8}$/; // Mii Studio does not allow lowe export default class Mii { public bitStream: ExtendedBitStream; + public buffer: Buffer; // Mii data // can be sure that these are all initialized in decode() @@ -150,92 +158,292 @@ export default class Mii { public checksum!: number; constructor(buffer: Buffer) { + this.buffer = buffer; this.bitStream = new ExtendedBitStream(buffer); this.decode(); } public validate(): void { // Size check - assert.equal(this.bitStream.length / 8, 0x60, `Invalid Mii data size. Got ${this.bitStream.length / 8}, expected 96`); + assert.equal( + this.bitStream.length / 8, + 0x60, + `Invalid Mii data size. Got ${this.bitStream.length / 8}, expected 96`, + ); // Value range and type checks - assert.ok(this.version === 0 || this.version === 3, `Invalid Mii version. Got ${this.version}, expected 0 or 3`); - assert.equal(typeof this.allowCopying, "boolean", `Invalid Mii allow copying. Got ${this.allowCopying}, expected true or false`); - assert.equal(typeof this.profanityFlag, "boolean", `Invalid Mii profanity flag. Got ${this.profanityFlag}, expected true or false`); - assert.ok(Util.inRange(this.regionLock, Util.range(4)), `Invalid Mii region lock. Got ${this.regionLock}, expected 0-3`); - assert.ok(Util.inRange(this.characterSet, Util.range(4)), `Invalid Mii region lock. Got ${this.characterSet}, expected 0-3`); - assert.ok(Util.inRange(this.pageIndex, Util.range(10)), `Invalid Mii page index. Got ${this.pageIndex}, expected 0-9`); - assert.ok(Util.inRange(this.slotIndex, Util.range(10)), `Invalid Mii slot index. Got ${this.slotIndex}, expected 0-9`); - assert.equal(this.unknown1, 0, `Invalid Mii unknown1. Got ${this.unknown1}, expected 0`); - assert.ok(Util.inRange(this.deviceOrigin, Util.range(1, 5)), `Invalid Mii device origin. Got ${this.deviceOrigin}, expected 1-4`); - assert.equal(this.systemId.length, 8, `Invalid Mii system ID size. Got ${this.systemId.length}, system IDs must be 8 bytes long`); - assert.equal(typeof this.normalMii, "boolean", `Invalid normal Mii flag. Got ${this.normalMii}, expected true or false`); - assert.equal(typeof this.dsMii, "boolean", `Invalid DS Mii flag. Got ${this.dsMii}, expected true or false`); - assert.equal(typeof this.nonUserMii, "boolean", `Invalid non-user Mii flag. Got ${this.nonUserMii}, expected true or false`); - assert.equal(typeof this.isValid, "boolean", `Invalid Mii valid flag. Got ${this.isValid}, expected true or false`); - assert.ok(this.creationTime < 268435456, `Invalid Mii creation time. Got ${this.creationTime}, max value for 28 bit integer is 268,435,456`); + assert.ok( + this.version === 0 || this.version === 3, + `Invalid Mii version. Got ${this.version}, expected 0 or 3`, + ); + assert.equal( + typeof this.allowCopying, + "boolean", + `Invalid Mii allow copying. Got ${this.allowCopying}, expected true or false`, + ); + assert.equal( + typeof this.profanityFlag, + "boolean", + `Invalid Mii profanity flag. Got ${this.profanityFlag}, expected true or false`, + ); + assert.ok( + Util.inRange(this.regionLock, Util.range(4)), + `Invalid Mii region lock. Got ${this.regionLock}, expected 0-3`, + ); + assert.ok( + Util.inRange(this.characterSet, Util.range(4)), + `Invalid Mii region lock. Got ${this.characterSet}, expected 0-3`, + ); + assert.ok( + Util.inRange(this.pageIndex, Util.range(10)), + `Invalid Mii page index. Got ${this.pageIndex}, expected 0-9`, + ); + assert.ok( + Util.inRange(this.slotIndex, Util.range(10)), + `Invalid Mii slot index. Got ${this.slotIndex}, expected 0-9`, + ); + assert.equal( + this.unknown1, + 0, + `Invalid Mii unknown1. Got ${this.unknown1}, expected 0`, + ); + assert.ok( + Util.inRange(this.deviceOrigin, Util.range(1, 5)), + `Invalid Mii device origin. Got ${this.deviceOrigin}, expected 1-4`, + ); + assert.equal( + this.systemId.length, + 8, + `Invalid Mii system ID size. Got ${this.systemId.length}, system IDs must be 8 bytes long`, + ); + assert.equal( + typeof this.normalMii, + "boolean", + `Invalid normal Mii flag. Got ${this.normalMii}, expected true or false`, + ); + assert.equal( + typeof this.dsMii, + "boolean", + `Invalid DS Mii flag. Got ${this.dsMii}, expected true or false`, + ); + assert.equal( + typeof this.nonUserMii, + "boolean", + `Invalid non-user Mii flag. Got ${this.nonUserMii}, expected true or false`, + ); + assert.equal( + typeof this.isValid, + "boolean", + `Invalid Mii valid flag. Got ${this.isValid}, expected true or false`, + ); + assert.ok( + this.creationTime < 268435456, + `Invalid Mii creation time. Got ${this.creationTime}, max value for 28 bit integer is 268,435,456`, + ); assert.equal( this.consoleMAC.length, 6, - `Invalid Mii console MAC address size. Got ${this.consoleMAC.length}, console MAC addresses must be 6 bytes long` + `Invalid Mii console MAC address size. Got ${this.consoleMAC.length}, console MAC addresses must be 6 bytes long`, + ); + assert.ok( + Util.inRange(this.gender, Util.range(2)), + `Invalid Mii gender. Got ${this.gender}, expected 0 or 1`, + ); + assert.ok( + Util.inRange(this.birthMonth, Util.range(13)), + `Invalid Mii birth month. Got ${this.birthMonth}, expected 0-12`, + ); + assert.ok( + Util.inRange(this.birthDay, Util.range(32)), + `Invalid Mii birth day. Got ${this.birthDay}, expected 0-31`, + ); + assert.ok( + Util.inRange(this.favoriteColor, Util.range(12)), + `Invalid Mii favorite color. Got ${this.favoriteColor}, expected 0-11`, + ); + assert.equal( + typeof this.favorite, + "boolean", + `Invalid favorite Mii flag. Got ${this.favorite}, expected true or false`, + ); + assert.ok( + Buffer.from(this.miiName, "utf16le").length <= 0x14, + `Invalid Mii name. Got ${this.miiName}, name may only be up to 10 characters`, + ); + assert.ok( + Util.inRange(this.height, Util.range(128)), + `Invalid Mii height. Got ${this.height}, expected 0-127`, + ); + assert.ok( + Util.inRange(this.build, Util.range(128)), + `Invalid Mii build. Got ${this.build}, expected 0-127`, + ); + assert.equal( + typeof this.disableSharing, + "boolean", + `Invalid disable sharing Mii flag. Got ${this.disableSharing}, expected true or false`, + ); + assert.ok( + Util.inRange(this.faceType, Util.range(12)), + `Invalid Mii face type. Got ${this.faceType}, expected 0-11`, + ); + assert.ok( + Util.inRange(this.skinColor, Util.range(7)), + `Invalid Mii skin color. Got ${this.skinColor}, expected 0-6`, + ); + assert.ok( + Util.inRange(this.wrinklesType, Util.range(12)), + `Invalid Mii wrinkles type. Got ${this.wrinklesType}, expected 0-11`, + ); + assert.ok( + Util.inRange(this.makeupType, Util.range(12)), + `Invalid Mii makeup type. Got ${this.makeupType}, expected 0-11`, + ); + assert.ok( + Util.inRange(this.hairType, Util.range(132)), + `Invalid Mii hair type. Got ${this.hairType}, expected 0-131`, ); - assert.ok(Util.inRange(this.gender, Util.range(2)), `Invalid Mii gender. Got ${this.gender}, expected 0 or 1`); - assert.ok(Util.inRange(this.birthMonth, Util.range(13)), `Invalid Mii birth month. Got ${this.birthMonth}, expected 0-12`); - assert.ok(Util.inRange(this.birthDay, Util.range(32)), `Invalid Mii birth day. Got ${this.birthDay}, expected 0-31`); - assert.ok(Util.inRange(this.favoriteColor, Util.range(12)), `Invalid Mii favorite color. Got ${this.favoriteColor}, expected 0-11`); - assert.equal(typeof this.favorite, "boolean", `Invalid favorite Mii flag. Got ${this.favorite}, expected true or false`); - assert.ok(Buffer.from(this.miiName, "utf16le").length <= 0x14, `Invalid Mii name. Got ${this.miiName}, name may only be up to 10 characters`); - assert.ok(Util.inRange(this.height, Util.range(128)), `Invalid Mii height. Got ${this.height}, expected 0-127`); - assert.ok(Util.inRange(this.build, Util.range(128)), `Invalid Mii build. Got ${this.build}, expected 0-127`); - assert.equal(typeof this.disableSharing, "boolean", `Invalid disable sharing Mii flag. Got ${this.disableSharing}, expected true or false`); - assert.ok(Util.inRange(this.faceType, Util.range(12)), `Invalid Mii face type. Got ${this.faceType}, expected 0-11`); - assert.ok(Util.inRange(this.skinColor, Util.range(7)), `Invalid Mii skin color. Got ${this.skinColor}, expected 0-6`); - assert.ok(Util.inRange(this.wrinklesType, Util.range(12)), `Invalid Mii wrinkles type. Got ${this.wrinklesType}, expected 0-11`); - assert.ok(Util.inRange(this.makeupType, Util.range(12)), `Invalid Mii makeup type. Got ${this.makeupType}, expected 0-11`); - assert.ok(Util.inRange(this.hairType, Util.range(132)), `Invalid Mii hair type. Got ${this.hairType}, expected 0-131`); // assert.ok(Util.inRange(this.hairColor, Util.range(8)), `Invalid Mii hair color. Got ${this.hairColor}, expected 0-7`); - assert.equal(typeof this.flipHair, "boolean", `Invalid flip hair flag. Got ${this.flipHair}, expected true or false`); - assert.ok(Util.inRange(this.eyeType, Util.range(60)), `Invalid Mii eye type. Got ${this.eyeType}, expected 0-59`); - assert.ok(Util.inRange(this.eyeColor, Util.range(6)), `Invalid Mii eye color. Got ${this.eyeColor}, expected 0-5`); - assert.ok(Util.inRange(this.eyeScale, Util.range(8)), `Invalid Mii eye scale. Got ${this.eyeScale}, expected 0-7`); - assert.ok(Util.inRange(this.eyeVerticalStretch, Util.range(7)), `Invalid Mii eye vertical stretch. Got ${this.eyeVerticalStretch}, expected 0-6`); - assert.ok(Util.inRange(this.eyeRotation, Util.range(8)), `Invalid Mii eye rotation. Got ${this.eyeRotation}, expected 0-7`); - assert.ok(Util.inRange(this.eyeSpacing, Util.range(13)), `Invalid Mii eye spacing. Got ${this.eyeSpacing}, expected 0-12`); - assert.ok(Util.inRange(this.eyeYPosition, Util.range(19)), `Invalid Mii eye Y position. Got ${this.eyeYPosition}, expected 0-18`); - assert.ok(Util.inRange(this.eyebrowType, Util.range(25)), `Invalid Mii eyebrow type. Got ${this.eyebrowType}, expected 0-24`); + assert.equal( + typeof this.flipHair, + "boolean", + `Invalid flip hair flag. Got ${this.flipHair}, expected true or false`, + ); + assert.ok( + Util.inRange(this.eyeType, Util.range(60)), + `Invalid Mii eye type. Got ${this.eyeType}, expected 0-59`, + ); + assert.ok( + Util.inRange(this.eyeColor, Util.range(6)), + `Invalid Mii eye color. Got ${this.eyeColor}, expected 0-5`, + ); + assert.ok( + Util.inRange(this.eyeScale, Util.range(8)), + `Invalid Mii eye scale. Got ${this.eyeScale}, expected 0-7`, + ); + assert.ok( + Util.inRange(this.eyeVerticalStretch, Util.range(7)), + `Invalid Mii eye vertical stretch. Got ${this.eyeVerticalStretch}, expected 0-6`, + ); + assert.ok( + Util.inRange(this.eyeRotation, Util.range(8)), + `Invalid Mii eye rotation. Got ${this.eyeRotation}, expected 0-7`, + ); + assert.ok( + Util.inRange(this.eyeSpacing, Util.range(13)), + `Invalid Mii eye spacing. Got ${this.eyeSpacing}, expected 0-12`, + ); + assert.ok( + Util.inRange(this.eyeYPosition, Util.range(19)), + `Invalid Mii eye Y position. Got ${this.eyeYPosition}, expected 0-18`, + ); + assert.ok( + Util.inRange(this.eyebrowType, Util.range(25)), + `Invalid Mii eyebrow type. Got ${this.eyebrowType}, expected 0-24`, + ); // assert.ok(Util.inRange(this.eyebrowColor, Util.range(8)), `Invalid Mii eyebrow color. Got ${this.eyebrowColor}, expected 0-7`); - assert.ok(Util.inRange(this.eyebrowScale, Util.range(9)), `Invalid Mii eyebrow scale. Got ${this.eyebrowScale}, expected 0-8`); + assert.ok( + Util.inRange(this.eyebrowScale, Util.range(9)), + `Invalid Mii eyebrow scale. Got ${this.eyebrowScale}, expected 0-8`, + ); assert.ok( Util.inRange(this.eyebrowVerticalStretch, Util.range(7)), - `Invalid Mii eyebrow vertical stretch. Got ${this.eyebrowVerticalStretch}, expected 0-6` + `Invalid Mii eyebrow vertical stretch. Got ${this.eyebrowVerticalStretch}, expected 0-6`, + ); + assert.ok( + Util.inRange(this.eyebrowRotation, Util.range(12)), + `Invalid Mii eyebrow rotation. Got ${this.eyebrowRotation}, expected 0-11`, + ); + assert.ok( + Util.inRange(this.eyebrowSpacing, Util.range(13)), + `Invalid Mii eyebrow spacing. Got ${this.eyebrowSpacing}, expected 0-12`, + ); + assert.ok( + Util.inRange(this.eyebrowYPosition, Util.range(3, 19)), + `Invalid Mii eyebrow Y position. Got ${this.eyebrowYPosition}, expected 3-18`, + ); + assert.ok( + Util.inRange(this.noseType, Util.range(18)), + `Invalid Mii nose type. Got ${this.noseType}, expected 0-17`, + ); + assert.ok( + Util.inRange(this.noseScale, Util.range(9)), + `Invalid Mii nose scale. Got ${this.noseScale}, expected 0-8`, + ); + assert.ok( + Util.inRange(this.noseYPosition, Util.range(19)), + `Invalid Mii nose Y position. Got ${this.noseYPosition}, expected 0-18`, + ); + assert.ok( + Util.inRange(this.mouthType, Util.range(36)), + `Invalid Mii mouth type. Got ${this.mouthType}, expected 0-35`, + ); + assert.ok( + Util.inRange(this.mouthColor, Util.range(5)), + `Invalid Mii mouth color. Got ${this.mouthColor}, expected 0-4`, + ); + assert.ok( + Util.inRange(this.mouthScale, Util.range(9)), + `Invalid Mii mouth scale. Got ${this.mouthScale}, expected 0-8`, ); - assert.ok(Util.inRange(this.eyebrowRotation, Util.range(12)), `Invalid Mii eyebrow rotation. Got ${this.eyebrowRotation}, expected 0-11`); - assert.ok(Util.inRange(this.eyebrowSpacing, Util.range(13)), `Invalid Mii eyebrow spacing. Got ${this.eyebrowSpacing}, expected 0-12`); - assert.ok(Util.inRange(this.eyebrowYPosition, Util.range(3, 19)), `Invalid Mii eyebrow Y position. Got ${this.eyebrowYPosition}, expected 3-18`); - assert.ok(Util.inRange(this.noseType, Util.range(18)), `Invalid Mii nose type. Got ${this.noseType}, expected 0-17`); - assert.ok(Util.inRange(this.noseScale, Util.range(9)), `Invalid Mii nose scale. Got ${this.noseScale}, expected 0-8`); - assert.ok(Util.inRange(this.noseYPosition, Util.range(19)), `Invalid Mii nose Y position. Got ${this.noseYPosition}, expected 0-18`); - assert.ok(Util.inRange(this.mouthType, Util.range(36)), `Invalid Mii mouth type. Got ${this.mouthType}, expected 0-35`); - assert.ok(Util.inRange(this.mouthColor, Util.range(5)), `Invalid Mii mouth color. Got ${this.mouthColor}, expected 0-4`); - assert.ok(Util.inRange(this.mouthScale, Util.range(9)), `Invalid Mii mouth scale. Got ${this.mouthScale}, expected 0-8`); assert.ok( Util.inRange(this.mouthHorizontalStretch, Util.range(7)), - `Invalid Mii mouth stretch. Got ${this.mouthHorizontalStretch}, expected 0-6` + `Invalid Mii mouth stretch. Got ${this.mouthHorizontalStretch}, expected 0-6`, + ); + assert.ok( + Util.inRange(this.mouthYPosition, Util.range(19)), + `Invalid Mii mouth Y position. Got ${this.mouthYPosition}, expected 0-18`, + ); + assert.ok( + Util.inRange(this.mustacheType, Util.range(6)), + `Invalid Mii mustache type. Got ${this.mustacheType}, expected 0-5`, + ); + assert.ok( + Util.inRange(this.beardType, Util.range(6)), + `Invalid Mii beard type. Got ${this.beardType}, expected 0-5`, ); - assert.ok(Util.inRange(this.mouthYPosition, Util.range(19)), `Invalid Mii mouth Y position. Got ${this.mouthYPosition}, expected 0-18`); - assert.ok(Util.inRange(this.mustacheType, Util.range(6)), `Invalid Mii mustache type. Got ${this.mustacheType}, expected 0-5`); - assert.ok(Util.inRange(this.beardType, Util.range(6)), `Invalid Mii beard type. Got ${this.beardType}, expected 0-5`); // assert.ok(Util.inRange(this.facialHairColor, Util.range(8)), `Invalid Mii beard type. Got ${this.facialHairColor}, expected 0-7`); - assert.ok(Util.inRange(this.mustacheScale, Util.range(9)), `Invalid Mii mustache scale. Got ${this.mustacheScale}, expected 0-8`); - assert.ok(Util.inRange(this.mustacheYPosition, Util.range(17)), `Invalid Mii mustache Y position. Got ${this.mustacheYPosition}, expected 0-16`); - assert.ok(Util.inRange(this.glassesType, Util.range(9)), `Invalid Mii glassess type. Got ${this.glassesType}, expected 0-8`); - assert.ok(Util.inRange(this.glassesColor, Util.range(6)), `Invalid Mii glassess type. Got ${this.glassesColor}, expected 0-5`); - assert.ok(Util.inRange(this.glassesScale, Util.range(8)), `Invalid Mii glassess type. Got ${this.glassesScale}, expected 0-7`); - assert.ok(Util.inRange(this.glassesYPosition, Util.range(21)), `Invalid Mii glassess Y position. Got ${this.glassesYPosition}, expected 0-20`); - assert.equal(typeof this.moleEnabled, "boolean", `Invalid mole enabled flag. Got ${this.moleEnabled}, expected true or false`); - assert.ok(Util.inRange(this.moleScale, Util.range(9)), `Invalid Mii mole scale. Got ${this.moleScale}, expected 0-8`); - assert.ok(Util.inRange(this.moleXPosition, Util.range(17)), `Invalid Mii mole X position. Got ${this.moleXPosition}, expected 0-16`); - assert.ok(Util.inRange(this.moleYPosition, Util.range(31)), `Invalid Mii mole Y position. Got ${this.moleYPosition}, expected 0-30`); + assert.ok( + Util.inRange(this.mustacheScale, Util.range(9)), + `Invalid Mii mustache scale. Got ${this.mustacheScale}, expected 0-8`, + ); + assert.ok( + Util.inRange(this.mustacheYPosition, Util.range(17)), + `Invalid Mii mustache Y position. Got ${this.mustacheYPosition}, expected 0-16`, + ); + assert.ok( + Util.inRange(this.glassesType, Util.range(9)), + `Invalid Mii glassess type. Got ${this.glassesType}, expected 0-8`, + ); + assert.ok( + Util.inRange(this.glassesColor, Util.range(6)), + `Invalid Mii glassess type. Got ${this.glassesColor}, expected 0-5`, + ); + assert.ok( + Util.inRange(this.glassesScale, Util.range(8)), + `Invalid Mii glassess type. Got ${this.glassesScale}, expected 0-7`, + ); + assert.ok( + Util.inRange(this.glassesYPosition, Util.range(21)), + `Invalid Mii glassess Y position. Got ${this.glassesYPosition}, expected 0-20`, + ); + assert.equal( + typeof this.moleEnabled, + "boolean", + `Invalid mole enabled flag. Got ${this.moleEnabled}, expected true or false`, + ); + assert.ok( + Util.inRange(this.moleScale, Util.range(9)), + `Invalid Mii mole scale. Got ${this.moleScale}, expected 0-8`, + ); + assert.ok( + Util.inRange(this.moleXPosition, Util.range(17)), + `Invalid Mii mole X position. Got ${this.moleXPosition}, expected 0-16`, + ); + assert.ok( + Util.inRange(this.moleYPosition, Util.range(31)), + `Invalid Mii mole Y position. Got ${this.moleYPosition}, expected 0-30`, + ); // Sanity checks /* @@ -251,7 +459,10 @@ export default class Mii { } */ - if (this.nonUserMii && (this.creationTime !== 0 || this.isValid || this.dsMii || this.normalMii)) { + if ( + this.nonUserMii && + (this.creationTime !== 0 || this.isValid || this.dsMii || this.normalMii) + ) { assert.fail("Non-user Mii's must have all other Mii ID bits set to 0"); } @@ -357,10 +568,12 @@ export default class Mii { } public calculateCRC(): number { - const view = this.bitStream.view; - - // @ts-expect-error _view is private - const data = view._view.subarray(0, 0x5e); + // #view is inaccessible + const data = new Uint8Array( + this.buffer.buffer, + this.buffer.byteOffset, + this.buffer.length, + ).subarray(0, 0x5e); let crc = 0x0000; @@ -506,7 +719,7 @@ export default class Mii { instanceCount?: number; instanceRotationMode?: string; data?: string; - } = STUDIO_RENDER_DEFAULTS + } = STUDIO_RENDER_DEFAULTS, ): string { const params = { ...STUDIO_RENDER_DEFAULTS, @@ -514,11 +727,23 @@ export default class Mii { data: this.encodeStudio().toString("hex"), }; - params.type = STUDIO_RENDER_TYPES.includes(params.type as string) ? params.type : STUDIO_RENDER_DEFAULTS.type; - params.expression = STUDIO_RENDER_EXPRESSIONS.includes(params.expression as string) ? params.expression : STUDIO_RENDER_DEFAULTS.expression; + params.type = STUDIO_RENDER_TYPES.includes(params.type as string) + ? params.type + : STUDIO_RENDER_DEFAULTS.type; + params.expression = STUDIO_RENDER_EXPRESSIONS.includes( + params.expression as string, + ) + ? params.expression + : STUDIO_RENDER_DEFAULTS.expression; params.width = Util.clamp(params.width, 512); - params.bgColor = STUDIO_BG_COLOR_REGEX.test(params.bgColor as string) ? params.bgColor : STUDIO_RENDER_DEFAULTS.bgColor; - params.clothesColor = STUDIO_RENDER_CLOTHES_COLORS.includes(params.clothesColor) ? params.clothesColor : STUDIO_RENDER_DEFAULTS.clothesColor; + params.bgColor = STUDIO_BG_COLOR_REGEX.test(params.bgColor as string) + ? params.bgColor + : STUDIO_RENDER_DEFAULTS.bgColor; + params.clothesColor = STUDIO_RENDER_CLOTHES_COLORS.includes( + params.clothesColor, + ) + ? params.clothesColor + : STUDIO_RENDER_DEFAULTS.clothesColor; params.cameraXRotate = Util.clamp(params.cameraXRotate, 359); params.cameraYRotate = Util.clamp(params.cameraYRotate, 359); params.cameraZRotate = Util.clamp(params.cameraZRotate, 359); @@ -528,16 +753,25 @@ export default class Mii { params.lightXDirection = Util.clamp(params.lightXDirection, 359); params.lightYDirection = Util.clamp(params.lightYDirection, 359); params.lightZDirection = Util.clamp(params.lightZDirection, 359); - params.lightDirectionMode = STUDIO_RENDER_LIGHT_DIRECTION_MODS.includes(params.lightDirectionMode) + params.lightDirectionMode = STUDIO_RENDER_LIGHT_DIRECTION_MODS.includes( + params.lightDirectionMode, + ) ? params.lightDirectionMode : STUDIO_RENDER_DEFAULTS.lightDirectionMode; params.instanceCount = Util.clamp(params.instanceCount, 1, 16); - params.instanceRotationMode = STUDIO_RENDER_INSTANCE_ROTATION_MODES.includes(params.instanceRotationMode) - ? params.instanceRotationMode - : STUDIO_RENDER_DEFAULTS.instanceRotationMode; + params.instanceRotationMode = + STUDIO_RENDER_INSTANCE_ROTATION_MODES.includes( + params.instanceRotationMode, + ) + ? params.instanceRotationMode + : STUDIO_RENDER_DEFAULTS.instanceRotationMode; // converts non-string params to strings - const query = new URLSearchParams(Object.fromEntries(Object.entries(params).map(([key, value]) => [key, value.toString()]))); + const query = new URLSearchParams( + Object.fromEntries( + Object.entries(params).map(([key, value]) => [key, value.toString()]), + ), + ); if (params.lightDirectionMode === "none") { query.delete("lightDirectionMode"); diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts index 9375669..c07b0d6 100644 --- a/src/lib/rate-limit.ts +++ b/src/lib/rate-limit.ts @@ -1,9 +1,9 @@ import { NextRequest, NextResponse } from "next/server"; -import { Redis } from "ioredis"; +import { createClient, RedisClientType } from "redis"; import { auth } from "./auth"; -const redis = new Redis(process.env.REDIS_URL!); -const windowSize = 60; +const WINDOW_SIZE = 60; +let client: RedisClientType | null = null; interface RateLimitData { success: boolean; @@ -12,6 +12,17 @@ interface RateLimitData { expires: number; } +async function getRedisClient() { + if (!client) { + client = createClient({ + url: process.env.REDIS_URL, + }); + client.on("error", (err) => console.error("Redis client error", err)); + await client.connect(); + } + return client; +} + // Fixed window implementation export class RateLimit { private request: NextRequest; @@ -37,22 +48,19 @@ export class RateLimit { const now = Date.now(); const seconds = Math.floor(now / 1000); - const currentWindow = Math.floor(seconds / windowSize) * windowSize; - const expireAt = currentWindow + windowSize; + const currentWindow = Math.floor(seconds / WINDOW_SIZE) * WINDOW_SIZE; + const expireAt = currentWindow + WINDOW_SIZE; try { - // Create a Redis transaction - const tx = redis.multi(); - tx.incr(key); - tx.expireat(key, expireAt); + const client = await getRedisClient(); - // Execute transaction and get the count - const results = await tx.exec(); - if (!results) { + // Execute a Redis transaction and get the count + const [result] = await client.multi().incr(key).expireAt(key, expireAt).exec(); + if (!result) { throw new Error("Redis transaction failed"); } - const count = results[0][1] as number; + const count = result as unknown as number; const success = count <= this.maxRequests; const remaining = Math.max(0, this.maxRequests - count); diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts index 9d237f7..a4713f4 100644 --- a/src/lib/schemas.ts +++ b/src/lib/schemas.ts @@ -30,7 +30,7 @@ export const tagsSchema = z .max(20, { error: "Tags cannot be more than 20 characters long" }) .regex(/^[a-z0-9-_]+$/, { error: "Tags can only contain lowercase letters, numbers, dashes, and underscores.", - }) + }), ) .min(1, { error: "There must be at least 1 tag" }) .max(8, { error: "There cannot be more than 8 tags" }); @@ -47,10 +47,20 @@ export const searchSchema = z.object({ value ?.split(",") .map((tag) => tag.trim()) - .filter((tag) => tag.length > 0) + .filter((tag) => tag.length > 0), + ), + exclude: z + .string() + .optional() + .transform((value) => + value + ?.split(",") + .map((tag) => tag.trim()) + .filter((tag) => tag.length > 0), ), platform: z.enum(MiiPlatform, { error: "Platform must be either 'THREE_DS', or 'SWITCH'" }).optional(), gender: z.enum(MiiGender, { error: "Gender must be either 'MALE', or 'FEMALE'" }).optional(), + allowCopying: z.coerce.boolean({ error: "Allow Copying must be either true or false" }).optional(), // todo: incorporate tagsSchema // Pages limit: z.coerce