diff --git a/.env b/.env
new file mode 100644
index 00000000..2b3940b2
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+VITE_API_URL=https://api.thecatapi.com/v1/
+VITE_API_KEY=live_7X4kYGgl0fqjcR3gRpHrL2XsLXdcFyeGFzreeXYRcGTxTMEMHLAbkYTpVbuZFg37
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..a547bf36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/README.md b/README.md
index 91d4d5b9..af1d42ed 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,109 @@
-# GlobalWebIndex Engineering Challenge
+# πΎ Meowbase β Stefanos Stamoulis
-## Exercise: CatLover
+A responsive, modern React application that fetches and displays cat images and breed data from **TheCatAPI**.
+Built with a cutting-edge React 19 + Vite toolchain, the project demonstrates **state management, caching, routing, accessibility, and testing practices**.
-Create a React application for cat lovers which is going to build upon thecatapi.com and will have 3 views.
-The **first** view displays a list of 10 random cat images and a button to load more. Clicking on any of those images opens a modal view with the image and the information about the catβs breed if available. This would be a link to the second view below - the breed detail. The modal should also contain a form to mark the image as your favourite (a part of the third view as well). Make sure you can copy-paste the URL of the modal and send it to your friends - they should see the same image as you can see.
+---
-The **second** view displays a list of cat breeds. Each breed opens a modal again with a list of cat images of that breed. Each of those images must be a link to the image detail from the previous point.
+## β¨ Demo
+[Live Demo](https://meowbase.pages.dev/)
-The **third** view allows you do the following things:
+---
-- Display your favourite cats
-- Remove an image from your favourites (use any UX option you like)
+## π Tech Stack
+This project is built with a modern frontend toolchain, optimized for **performance, developer experience, and scalability**:
-You can find the API documentation here: https://developers.thecatapi.com/
-We give you a lot of freedom in technologies and ways of doing things. We only insist on you using React.js. Get creative as much as you want, we WILL appreciate it. You will not be evaluated based on how well you follow these instructions, but based on how sensible your solution will be. In case you are not able to implement something you would normally implement for time reasons, make it clear with a comment.
+- **React 19** β UI foundation
+- **Vite 7** β Lightning-fast dev/build tooling
+- **TypeScript 5** β Type safety & DX
+- **React Router v7** β Client-side routing
+- **Redux Toolkit** β State management made simple
+- **TanStack React Query v5** β Data fetching, caching & syncing
+- **React Content Loader** β Skeleton loaders during fetches
+- **React to Print** β Export/print support
+- **Sass** β Styling with flexibility and modularity
-## Submission
+---
-Once you have built your app, share your code in the mean suits you best
-Good luck, potential colleague!
+## βοΈ Getting Started
+
+### Prerequisites
+- Node.js v18+
+- npm
+
+### Setup
+
+# Clone the repo
+```
+git clone https://github.com/StefanosSt/Meowbase.git
+cd meowbase
+```
+
+# Install dependencies
+```
+npm install
+```
+# Start development
+```
+npm run dev
+```
+---
+
+## π Project Structure
+
+```
+src/
+βββ api/ # API clients and endpoints (TheCatAPI integration)
+βββ assets/ # includes images & icons
+βββ components/ # Reusable UI components
+βββ hooks/ # Custom React hooks
+βββ pages/ # Route-based pages
+βββ routes/ # Route endpoints setup
+βββ store/ # Redux store setup
+βββ styles/ # Global Sass styles
+βββ types/ # Shared TypeScript types
+```
+
+---
+
+## π§Ή Code Quality & Formatting
+Consistency and maintainability are enforced via:
+
+- **ESLint** β Static code analysis
+- **Prettier** β Consistent formatting
+- **TypeScript ESLint plugin** β Advanced type linting
+
+---
+
+## βΏ Accessibility (a11y)
+
+- Semantic markup for screen readers
+- Keyboard-friendly navigation
+- Skeleton loaders to improve perceived performance
+
+---
+
+## π¨ Styling & Theming
+
+- Built with Sass for modular, maintainable styling
+- Structure allows for easy integration of light/dark modes in the future
+
+---
+
+## π§ Improvements & Future Work
+
+- Add integration/E2E tests (Cypress or Playwright)
+- Centralize Modal logic in order to share functionality across the app
+- Internationalization (i18n) for multilingual support
+- Improvements on A11y
+
+## π Summary
+
+### Meowbase showcases:
+
+- Modern frontend stack (React 19 + Vite + TypeScript)
+- Scalable architecture (Redux Toolkit + React Query)
+- Testing setup for reliability
+- Clean, extensible project structure
+
+π± Built with β€οΈ for cat lovers and developers alike.
\ No newline at end of file
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 00000000..41baed0b
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,25 @@
+import js from '@eslint/js';
+import globals from 'globals';
+import reactHooks from 'eslint-plugin-react-hooks';
+import reactRefresh from 'eslint-plugin-react-refresh';
+import tseslint from 'typescript-eslint';
+import prettier from 'eslint-config-prettier';
+import { defineConfig, globalIgnores } from 'eslint/config';
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs['recommended-latest'],
+ reactRefresh.configs.vite,
+ prettier,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+]);
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..858511a0
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ MeowBase |
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..b470ecda
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,5148 @@
+{
+ "name": "mewobase-starter",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "mewobase-starter",
+ "version": "0.0.0",
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.9.0",
+ "@tanstack/react-query": "^5.89.0",
+ "react": "^19.1.1",
+ "react-content-loader": "^7.1.1",
+ "react-dom": "^19.1.1",
+ "react-redux": "^9.2.0",
+ "react-router": "^7.9.1",
+ "react-router-dom": "^7.9.1",
+ "react-to-print": "^3.1.1",
+ "vite-tsconfig-paths": "^5.1.4"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.35.0",
+ "@testing-library/jest-dom": "^6.8.0",
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^14.6.1",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react-swc": "^4.0.1",
+ "eslint": "^9.35.0",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.4",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.4.0",
+ "jsdom": "^27.0.0",
+ "prettier": "^3.6.2",
+ "sass": "^1.93.0",
+ "typescript": "~5.8.3",
+ "typescript-eslint": "^8.43.0",
+ "vite": "^7.1.6",
+ "vitest": "^3.2.4"
+ }
+ },
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
+ "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.5.tgz",
+ "integrity": "sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-color-parser": "^3.1.0",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "lru-cache": "^11.2.1"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "6.5.6",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.6.tgz",
+ "integrity": "sha512-Mj3Hu9ymlsERd7WOsUKNUZnJYL4IZ/I9wVVYgtvOsWYiEFbkQ4G7VRIh2USxTVW4BBDIsLG+gBUgqOqf2Kvqow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.1.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.2.1"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz",
+ "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz",
+ "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz",
+ "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz",
+ "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz",
+ "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz",
+ "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz",
+ "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz",
+ "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz",
+ "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz",
+ "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz",
+ "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz",
+ "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz",
+ "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz",
+ "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz",
+ "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz",
+ "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz",
+ "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz",
+ "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz",
+ "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz",
+ "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz",
+ "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz",
+ "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz",
+ "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz",
+ "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz",
+ "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz",
+ "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz",
+ "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
+ "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
+ "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.36.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz",
+ "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
+ "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.15.2",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-freebsd-x64": "2.5.1",
+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-win32-ia32": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+ "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+ "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+ "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+ "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+ "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+ "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+ "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+ "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+ "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+ "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+ "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+ "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz",
+ "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@standard-schema/utils": "^0.3.0",
+ "immer": "^10.0.3",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.35",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.35.tgz",
+ "integrity": "sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
+ "integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz",
+ "integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz",
+ "integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz",
+ "integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz",
+ "integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz",
+ "integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz",
+ "integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz",
+ "integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz",
+ "integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz",
+ "integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz",
+ "integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz",
+ "integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz",
+ "integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz",
+ "integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz",
+ "integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz",
+ "integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz",
+ "integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz",
+ "integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz",
+ "integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz",
+ "integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz",
+ "integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz",
+ "integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@swc/core": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz",
+ "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "@swc/types": "^0.1.24"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/swc"
+ },
+ "optionalDependencies": {
+ "@swc/core-darwin-arm64": "1.13.5",
+ "@swc/core-darwin-x64": "1.13.5",
+ "@swc/core-linux-arm-gnueabihf": "1.13.5",
+ "@swc/core-linux-arm64-gnu": "1.13.5",
+ "@swc/core-linux-arm64-musl": "1.13.5",
+ "@swc/core-linux-x64-gnu": "1.13.5",
+ "@swc/core-linux-x64-musl": "1.13.5",
+ "@swc/core-win32-arm64-msvc": "1.13.5",
+ "@swc/core-win32-ia32-msvc": "1.13.5",
+ "@swc/core-win32-x64-msvc": "1.13.5"
+ },
+ "peerDependencies": {
+ "@swc/helpers": ">=0.5.17"
+ },
+ "peerDependenciesMeta": {
+ "@swc/helpers": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@swc/core-darwin-arm64": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz",
+ "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-darwin-x64": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz",
+ "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm-gnueabihf": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz",
+ "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-gnu": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz",
+ "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-musl": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz",
+ "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-gnu": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz",
+ "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-musl": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz",
+ "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-arm64-msvc": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz",
+ "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-ia32-msvc": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz",
+ "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-x64-msvc": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz",
+ "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@swc/types": {
+ "version": "0.1.25",
+ "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
+ "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3"
+ }
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.90.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.2.tgz",
+ "integrity": "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.90.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.2.tgz",
+ "integrity": "sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.90.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
+ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz",
+ "integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "picocolors": "^1.1.1",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz",
+ "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.6.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
+ "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz",
+ "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.13",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
+ "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.1.9",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz",
+ "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.0.0"
+ }
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz",
+ "integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.44.1",
+ "@typescript-eslint/type-utils": "8.44.1",
+ "@typescript-eslint/utils": "8.44.1",
+ "@typescript-eslint/visitor-keys": "8.44.1",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.44.1",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz",
+ "integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.44.1",
+ "@typescript-eslint/types": "8.44.1",
+ "@typescript-eslint/typescript-estree": "8.44.1",
+ "@typescript-eslint/visitor-keys": "8.44.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz",
+ "integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.44.1",
+ "@typescript-eslint/types": "^8.44.1",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz",
+ "integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.44.1",
+ "@typescript-eslint/visitor-keys": "8.44.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz",
+ "integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz",
+ "integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.44.1",
+ "@typescript-eslint/typescript-estree": "8.44.1",
+ "@typescript-eslint/utils": "8.44.1",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz",
+ "integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz",
+ "integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.44.1",
+ "@typescript-eslint/tsconfig-utils": "8.44.1",
+ "@typescript-eslint/types": "8.44.1",
+ "@typescript-eslint/visitor-keys": "8.44.1",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.1.tgz",
+ "integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.44.1",
+ "@typescript-eslint/types": "8.44.1",
+ "@typescript-eslint/typescript-estree": "8.44.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz",
+ "integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.44.1",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react-swc": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.1.0.tgz",
+ "integrity": "sha512-Ff690TUck0Anlh7wdIcnsVMhofeEVgm44Y4OYdeeEEPSKyZHzDI9gfVBvySEhDfXtBp8tLCbfsVKPWEMEjq8/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "1.0.0-beta.35",
+ "@swc/core": "^1.13.5"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4 || ^5 || ^6 || ^7"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
+ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
+ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.2.4",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
+ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
+ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.4",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
+ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
+ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
+ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-tree": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.12.2",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cssstyle": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz",
+ "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^4.0.3",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.14",
+ "css-tree": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/data-urls": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
+ "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
+ "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.10",
+ "@esbuild/android-arm": "0.25.10",
+ "@esbuild/android-arm64": "0.25.10",
+ "@esbuild/android-x64": "0.25.10",
+ "@esbuild/darwin-arm64": "0.25.10",
+ "@esbuild/darwin-x64": "0.25.10",
+ "@esbuild/freebsd-arm64": "0.25.10",
+ "@esbuild/freebsd-x64": "0.25.10",
+ "@esbuild/linux-arm": "0.25.10",
+ "@esbuild/linux-arm64": "0.25.10",
+ "@esbuild/linux-ia32": "0.25.10",
+ "@esbuild/linux-loong64": "0.25.10",
+ "@esbuild/linux-mips64el": "0.25.10",
+ "@esbuild/linux-ppc64": "0.25.10",
+ "@esbuild/linux-riscv64": "0.25.10",
+ "@esbuild/linux-s390x": "0.25.10",
+ "@esbuild/linux-x64": "0.25.10",
+ "@esbuild/netbsd-arm64": "0.25.10",
+ "@esbuild/netbsd-x64": "0.25.10",
+ "@esbuild/openbsd-arm64": "0.25.10",
+ "@esbuild/openbsd-x64": "0.25.10",
+ "@esbuild/openharmony-arm64": "0.25.10",
+ "@esbuild/sunos-x64": "0.25.10",
+ "@esbuild/win32-arm64": "0.25.10",
+ "@esbuild/win32-ia32": "0.25.10",
+ "@esbuild/win32-x64": "0.25.10"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.36.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz",
+ "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.1",
+ "@eslint/core": "^0.15.2",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.36.0",
+ "@eslint/plugin-kit": "^0.3.5",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz",
+ "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.11.7"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.21",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.21.tgz",
+ "integrity": "sha512-MWDWTtNC4voTcWDxXbdmBNe8b/TxfxRFUL6hXgKWJjN9c1AagYEmpiFWBWzDw+5H3SulWUe1pJKTnoSdmk88UA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz",
+ "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
+ "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globrex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
+ "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
+ "license": "MIT"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immer": {
+ "version": "10.1.3",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz",
+ "integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
+ "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "27.0.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz",
+ "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/dom-selector": "^6.5.4",
+ "cssstyle": "^5.3.0",
+ "data-urls": "^6.0.0",
+ "decimal.js": "^10.5.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "parse5": "^7.3.0",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^6.0.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^8.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^15.0.0",
+ "ws": "^8.18.2",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "11.2.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz",
+ "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.19",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
+ "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-content-loader": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/react-content-loader/-/react-content-loader-7.1.1.tgz",
+ "integrity": "sha512-yNkqtd+15wXRLfDKZb5nTqDV2fPTG2kpUgeGRb+WBz43bU0j4DSGXETF0bnFr44fAoTPpm0Dya0WGdhpHSvtYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
+ "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.1"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/react-redux": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+ "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.9.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.1.tgz",
+ "integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.9.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.1.tgz",
+ "integrity": "sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.9.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/react-to-print": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-3.1.1.tgz",
+ "integrity": "sha512-N0MUMhpl8nkGri13BjP7zusj3B/j+1eMOTt8N8PYuhBYGzA4PqTXqcihJ9cZw996dvhV6mBdwafIQCg3Ap5bKg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ~19"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "license": "MIT"
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.52.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
+ "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.52.2",
+ "@rollup/rollup-android-arm64": "4.52.2",
+ "@rollup/rollup-darwin-arm64": "4.52.2",
+ "@rollup/rollup-darwin-x64": "4.52.2",
+ "@rollup/rollup-freebsd-arm64": "4.52.2",
+ "@rollup/rollup-freebsd-x64": "4.52.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.52.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.52.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.52.2",
+ "@rollup/rollup-linux-arm64-musl": "4.52.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.52.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.52.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.52.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.52.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.52.2",
+ "@rollup/rollup-linux-x64-gnu": "4.52.2",
+ "@rollup/rollup-linux-x64-musl": "4.52.2",
+ "@rollup/rollup-openharmony-arm64": "4.52.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.52.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.52.2",
+ "@rollup/rollup-win32-x64-gnu": "4.52.2",
+ "@rollup/rollup-win32-x64-msvc": "4.52.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sass": {
+ "version": "1.93.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.1.tgz",
+ "integrity": "sha512-wLAeLB7IksO2u+cCfhHqcy7/2ZUMPp/X2oV6+LjmweTqgjhOKrkaE/Q1wljxtco5EcOcupZ4c981X0gpk5Tiag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^4.0.0",
+ "immutable": "^5.0.2",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher": "^2.4.1"
+ }
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
+ "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz",
+ "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/synckit": {
+ "version": "0.11.11",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
+ "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
+ "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "7.0.16",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz",
+ "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^7.0.16"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "7.0.16",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz",
+ "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
+ "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tsconfck": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz",
+ "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==",
+ "license": "MIT",
+ "bin": {
+ "tsconfck": "bin/tsconfck.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.44.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.1.tgz",
+ "integrity": "sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.44.1",
+ "@typescript-eslint/parser": "8.44.1",
+ "@typescript-eslint/typescript-estree": "8.44.1",
+ "@typescript-eslint/utils": "8.44.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
+ "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite-tsconfig-paths": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz",
+ "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "globrex": "^0.1.2",
+ "tsconfck": "^3.0.3"
+ },
+ "peerDependencies": {
+ "vite": "*"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
+ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.4",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/pretty-format": "^3.2.4",
+ "@vitest/runner": "3.2.4",
+ "@vitest/snapshot": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.4",
+ "@vitest/ui": "3.2.4",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
+ "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
+ "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..2e793a18
--- /dev/null
+++ b/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "meowbase",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "format": "prettier --write .",
+ "format:check": "prettier --check .",
+ "preview": "vite preview",
+ "test": "vitest"
+ },
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.9.0",
+ "@tanstack/react-query": "^5.89.0",
+ "react": "^19.1.1",
+ "react-content-loader": "^7.1.1",
+ "react-dom": "^19.1.1",
+ "react-redux": "^9.2.0",
+ "react-router": "^7.9.1",
+ "react-router-dom": "^7.9.1",
+ "react-to-print": "^3.1.1",
+ "vite-tsconfig-paths": "^5.1.4"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.35.0",
+ "@testing-library/jest-dom": "^6.8.0",
+ "@testing-library/react": "^16.3.0",
+ "@testing-library/user-event": "^14.6.1",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react-swc": "^4.0.1",
+ "eslint": "^9.35.0",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.4",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.4.0",
+ "jsdom": "^27.0.0",
+ "prettier": "^3.6.2",
+ "sass": "^1.93.0",
+ "typescript": "~5.8.3",
+ "typescript-eslint": "^8.43.0",
+ "vite": "^7.1.6",
+ "vitest": "^3.2.4"
+ }
+}
diff --git a/public/meowbase.png b/public/meowbase.png
new file mode 100644
index 00000000..d0a40cd2
Binary files /dev/null and b/public/meowbase.png differ
diff --git a/public/vite.svg b/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 00000000..be810211
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,15 @@
+import { RouterProvider } from 'react-router-dom';
+import { router } from '@/routes/routes';
+import Toast from '@components/ui/Toast/Toast';
+import '@styles/index.scss';
+
+function App() {
+ return (
+ <>
+
+
+ >
+ )
+}
+
+export default App
diff --git a/src/api/apiService.ts b/src/api/apiService.ts
new file mode 100644
index 00000000..a0bd0ffb
--- /dev/null
+++ b/src/api/apiService.ts
@@ -0,0 +1,132 @@
+import { setLoading } from '@store/loadingSlice';
+import { store } from '@store/store';
+import { totalCount } from '@store/favoritesCounterSlice';
+
+const getApiConfig = () => ({
+ baseUrl: import.meta.env.VITE_API_URL,
+ apiKey: import.meta.env.VITE_API_KEY,
+});
+
+// Base request function without loading state
+const baseRequest = (endpoint: string, options: RequestInit = {}): Promise => {
+ const { baseUrl, apiKey } = getApiConfig();
+ const url = `${baseUrl}${endpoint}`;
+
+ return fetch(url, {
+ headers: {
+ 'x-api-key': apiKey,
+ 'Content-Type': 'application/json',
+ ...options.headers,
+ },
+ ...options,
+ })
+ .then((response) => {
+ if (!response.ok) {
+ const error = new Error('An error occurred while fetching data.') as Error & { code?: number; info?: unknown };
+ error.code = response.status;
+
+ return response.json()
+ .then((data) => {
+ error.info = data;
+ throw error;
+ })
+ .catch(() => {
+ error.info = response.statusText;
+ throw error;
+ });
+ }
+ return response.json();
+ });
+};
+
+// Request function for fetching data (with loading state)
+const request = async (endpoint: string, options: RequestInit = {}): Promise => {
+ store.dispatch(setLoading(true));
+
+ try {
+ return await baseRequest(endpoint, options);
+ } finally {
+ store.dispatch(setLoading(false));
+ }
+};
+
+// Request function for mutations (without loading state)
+const mutationRequest = async (endpoint: string, options: RequestInit = {}): Promise => {
+ return baseRequest(endpoint, options);
+};
+
+const buildQuery = (params: Record): string => {
+ const searchParams = new URLSearchParams();
+
+ Object.entries(params).forEach(([key, value]) => {
+ if (value !== undefined && value !== null) {
+ if (typeof value === 'boolean') {
+ searchParams.append(key, value ? '1' : '0');
+ } else {
+ searchParams.append(key, String(value));
+ }
+ }
+ });
+
+ return searchParams.toString();
+};
+
+const fetchData = async (endpoint: string, params: Record = {}) => {
+ const queryString = buildQuery(params);
+ const fullEndpoint = `${endpoint}${queryString ? `?${queryString}` : ''}`;
+ return request(fullEndpoint);
+};
+
+// Fetch cats for Catlist page receiving params(has_breeds)
+const fetchCats = async (params: Record = {}) => {
+ return fetchData('images/search', params);
+};
+
+// Fetch Breeds for Breeds page
+const fetchBreeds = async (params: Record = {}) => {
+ return fetchData('breeds', params);
+};
+
+// Fetch Favorite Cats passing params of sub_id
+const fetchFavorites = async (params: Record = {}) => {
+ const result = await fetchData('favourites', params);
+ if (Array.isArray(result)) {
+ store.dispatch(totalCount(result.length));
+ }
+ return result;
+};
+
+// Add a cat in Favorites
+const addToFavorites = async (imageId: string, subId?: string) => {
+ return mutationRequest('favourites', {
+ method: 'POST',
+ body: JSON.stringify({ image_id: imageId, sub_id: subId }),
+ });
+};
+
+// Remove a cat from Favorites
+const removeFromFavorites = async (favoriteId: number) => {
+ return mutationRequest(`favourites/${favoriteId}`, { method: 'DELETE' });
+};
+
+// Fetch a specific cat with id
+const fetchCatById = async (catId: string) => {
+ return request(`images/${catId}`);
+};
+
+// Fetch all the cats of a specific Breed
+const fetchCatsByBreed = async (breedId: string, limit: number = 10) => {
+ return fetchData('images/search', { limit, breed_ids: breedId });
+};
+
+// Export the API service as an object with all methods
+export const apiService = {
+ fetchData,
+ fetchCats,
+ fetchBreeds,
+ fetchFavorites,
+ addToFavorites,
+ removeFromFavorites,
+ fetchCatById,
+ fetchCatsByBreed
+};
\ No newline at end of file
diff --git a/src/api/hooks.ts b/src/api/hooks.ts
new file mode 100644
index 00000000..4fa9137e
--- /dev/null
+++ b/src/api/hooks.ts
@@ -0,0 +1,78 @@
+import { useQuery, useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { apiService } from './apiService';
+
+
+// Dynamic infinite cats with toggleable parameters
+export function useDynamicInfiniteCats(params = {}, limit = 10) {
+ return useInfiniteQuery({
+ queryKey: ['cats-infinite-dynamic', params, limit],
+ queryFn: ({ pageParam = 0 }) => apiService.fetchCats({ ...params, limit, page: pageParam }),
+ getNextPageParam: (lastPage, pages) => {
+ if (Array.isArray(lastPage) && lastPage.length < limit) return undefined;
+ return pages.length;
+ },
+ staleTime: 1000 * 60 * 5,
+ initialPageParam: 0,
+ });
+}
+
+// Fetch all breeds
+export function useBreeds() {
+ return useQuery({
+ queryKey: ['breeds'],
+ queryFn: () => apiService.fetchBreeds(),
+ staleTime: 1000 * 60 * 10,
+ });
+}
+
+// Fetch Favorites
+export function useFavorites(params = {}) {
+ return useQuery({
+ queryKey: ['favorites', params],
+ queryFn: () => apiService.fetchFavorites(params),
+ staleTime: 1000 * 60 * 2,
+ });
+}
+
+// Add to Favorites
+export function useAddToFavorites() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ imageId, subId }: { imageId: string; subId?: string }) =>
+ apiService.addToFavorites(imageId, subId),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['favorites'] });
+ },
+ });
+}
+
+// Remove from Favorites
+export function useRemoveFromFavorites() {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (favoriteId: number) => apiService.removeFromFavorites(favoriteId),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['favorites'] });
+ },
+ });
+}
+
+// Fetch single cat by ID
+export function useCatById(catId: string, options = {}) {
+ return useQuery({
+ queryKey: ['cat', catId],
+ queryFn: () => apiService.fetchCatById(catId),
+ staleTime: 1000 * 60 * 5,
+ ...options,
+ });
+}
+
+// Fetch cats by breed ID
+export function useCatsByBreed(breedId: string, limit = 10, options = {}) {
+ return useQuery({
+ queryKey: ['cats-by-breed', breedId, limit],
+ queryFn: () => apiService.fetchCatsByBreed(breedId, limit),
+ staleTime: 1000 * 60 * 5,
+ ...options,
+ });
+}
diff --git a/src/assets/404.png b/src/assets/404.png
new file mode 100644
index 00000000..96d45a71
Binary files /dev/null and b/src/assets/404.png differ
diff --git a/src/assets/cat.png b/src/assets/cat.png
new file mode 100644
index 00000000..30b77f18
Binary files /dev/null and b/src/assets/cat.png differ
diff --git a/src/assets/icons/icons.tsx b/src/assets/icons/icons.tsx
new file mode 100644
index 00000000..56e7027c
--- /dev/null
+++ b/src/assets/icons/icons.tsx
@@ -0,0 +1,160 @@
+type Props = {
+ width?: number | string;
+ height?: number | string;
+ className?: string;
+};
+
+export const Logo = ({ width = 40, height = 50, className = 'logo' }: Props) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const HamburgerMenu = ({ width = 24, height = 24, className = '' }: Props) => {
+ return (
+
+
+
+ );
+};
+
+export const Linkedin = ({ width = 24, height = 24, className = '' }: Props) => {
+ return (
+
+ );
+};
+
+export const Gmail = ({ width = 24, height = 24, className = '' }: Props) => {
+ return (
+
+ );
+};
+
+export const Github = ({ width = 24, height = 24, className = '' }: Props) => {
+ return (
+
+ );
+};
+
+export const Heart = ({ width = 24, height = 24, className = '' }: Props) => {
+ return (
+
+ );
+};
+
+export const Print = ({ width = 24, height = 24, className = '' }: Props) => {
+ return (
+
+ )
+};
+
+export const Share = ({ width = 24, height = 24, className = ''}: Props) => {
+ return (
+
+ )
+};
+
+export const Download = ({ width = 24, height = 24, className = ''}: Props) => {
+ return (
+
+ )
+}
+
+export const Star = ({ width = 16, height = 16, className = ''}: Props) => {
+ return (
+
+
+
+ )
+}
+
+export const Arrow = ({ width = 24, height = 24, className = ''}: Props) => {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/assets/react.svg b/src/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/common/BreedDetails/BreedDetails.module.scss b/src/components/common/BreedDetails/BreedDetails.module.scss
new file mode 100644
index 00000000..b8b479a4
--- /dev/null
+++ b/src/components/common/BreedDetails/BreedDetails.module.scss
@@ -0,0 +1,210 @@
+@use '../../../styles/variables' as *;
+
+.breedDetails {
+ padding: $spacing-lg 0;
+ color: $color-grey;
+ max-width: 100%;
+
+ &__header {
+ margin-bottom: $spacing-lg;
+ text-align: center;
+ border-bottom: 1px solid #e0e0e0;
+ padding-bottom: $spacing-md;
+ }
+
+ &__origin {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $spacing-sm;
+ font-size:$font-size-md;
+ color: $color-grey;
+ }
+
+ &__country {
+ font-weight: 500;
+ }
+
+ &__code {
+ font-weight: 300;
+ font-style: italic;
+ }
+
+ &__description {
+ margin-bottom: $spacing-lg;
+
+ p {
+ margin: 0;
+ line-height: 1.6;
+ font-size: $font-size-md;
+ color: $color-grey;
+ text-align: justify;
+ }
+ }
+
+ &__info {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: $spacing-md;
+ margin-bottom: $spacing-lg;
+ padding: $spacing-md;
+ background-color: $color-background-light;
+ border-radius: $radius-sm;
+
+ @media (max-width: 480px) {
+ grid-template-columns: 1fr;
+ }
+ }
+
+ &__infoItem {
+ display: flex;
+ flex-direction: column;
+ gap: $spacing-xs;
+ }
+
+ &__label {
+ font-size: $font-size-sm;
+ font-weight: 600;
+ color: $color-grey;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ }
+
+ &__value {
+ font-size: $font-size-md;
+ font-weight: 500;
+ color: $color-text;
+ }
+
+ &__temperament,
+ &__ratings,
+ &__features {
+ margin-bottom: $spacing-lg;
+ }
+
+ &__sectionTitle {
+ font-size: $font-size-lg;
+ font-weight: 600;
+ margin: 0 0 $spacing-md 0;
+ color: $color-text;
+ border-left: 4px solid $color-secondary;
+ padding-left: $spacing-sm;
+ }
+
+ &__traits {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $spacing-sm;
+ }
+
+ &__trait {
+ display: inline-block;
+ padding: $spacing-xs $spacing-md;
+ background-color: $color-background-light;
+ border: 1px solid $color-secondary;
+ border-radius: $radius-md;
+ font-size: $font-size-sm;
+ font-weight: 500;
+ color: $color-text;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background-color: $color-secondary;
+ color: $color-white;
+ }
+ }
+
+ &__ratingsGrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: $spacing-md;
+
+ @media (max-width: 768px) {
+ grid-template-columns: 1fr;
+ }
+ }
+
+ &__ratingItem {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: $spacing-xs;
+ background-color: $color-background-light;
+ border-radius: $radius-sm;
+ border: 1px solid $color-border-light;
+ transition: all 0.2s ease;
+
+ &:hover {
+ border-color: $color-secondary;
+ background-color: $color-background-light;
+ }
+ }
+
+ &__ratingLabel {
+ font-size: $font-size-sm;
+ font-weight: 500;
+ color: $color-grey;
+ min-width: 0;
+ flex: 1;
+ margin-right: $spacing-md;
+ }
+
+ &__featuresList {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $spacing-sm;
+ }
+
+ &__feature {
+ display: inline-flex;
+ align-items: center;
+ padding: $spacing-sm $spacing-md;
+ background-color: $color-background-dark;
+ color: $color-white;
+ border-radius: $radius-sm;
+ font-size: $font-size-sm;
+ font-weight: 500;
+ position: relative;
+
+ &::before {
+ content: 'β';
+ margin-right: $spacing-xs;
+ font-weight: bold;
+ }
+ }
+
+ @media (max-width: 768px) {
+ padding: $spacing-md 0;
+
+ &__name {
+ font-size: $font-size-lg;
+ }
+
+ &__info {
+ padding: $spacing-md;
+ }
+
+ &__sectionTitle {
+ font-size: $font-size-md;
+ }
+ }
+
+ @media (max-width: 480px) {
+ &__name {
+ font-size: $font-size-lg
+ }
+
+ &__origin {
+ flex-direction: column;
+ gap: $spacing-xs;
+ }
+
+ &__ratingsGrid {
+ gap: $spacing-sm;
+ }
+
+ &__ratingItem {
+ padding: $spacing-xs;
+ }
+ }
+}
diff --git a/src/components/common/BreedDetails/BreedDetails.tsx b/src/components/common/BreedDetails/BreedDetails.tsx
new file mode 100644
index 00000000..7ec57c56
--- /dev/null
+++ b/src/components/common/BreedDetails/BreedDetails.tsx
@@ -0,0 +1,117 @@
+import Rating from '@components/common/Rating/Rating';
+import styles from './BreedDetails.module.scss';
+import type { BreedData, ModalDetails } from '@types';
+
+
+interface BreedDetailsProps {
+ breed: BreedData | ModalDetails;
+}
+
+const BreedDetails = ({ breed }: BreedDetailsProps) => {
+ const temperamentTraits = breed.temperament?.split(', ') || [];
+
+ // Helper for safe dynamic property access
+ function getBreedNumberProp(obj: T, key: string): number {
+ const value = (obj as Record)[key];
+ return typeof value === 'number' ? value : 0;
+ }
+
+ return (
+
+
+
{breed.name}
+
+ {breed.origin}
+ {breed.country_code && ({breed.country_code}) }
+
+
+
+
+
+
+
+ Life Span:
+ {breed.life_span} years
+
+ {breed.weight && (
+
+ Weight:
+
+ {breed.weight.metric} kg ({breed.weight.imperial} lbs)
+
+
+ )}
+
+
+
+
Temperament
+
+ {temperamentTraits.map((trait, index) => (
+
+ {trait.trim()}
+
+ ))}
+
+
+
+
+
Characteristics
+
+ {(() => {
+ const ratingFields: { [key: string]: string } = {
+ adaptability: 'Adaptability' ,
+ affection_level: 'Affection Level' ,
+ child_friendly: 'Child Friendly' ,
+ dog_friendly: 'Dog Friendly' ,
+ energy_level: 'Energy Level' ,
+ grooming: 'Grooming' ,
+ health_issues: 'Health Issues' ,
+ intelligence: 'Intelligence' ,
+ shedding_level: 'Shedding Level' ,
+ social_needs: 'Social Needs' ,
+ stranger_friendly: 'Stranger Friendly' ,
+ vocalisation: 'Vocalisation' };
+ return Object.entries(ratingFields).map(([key, label]) => (
+
+ {label}
+
+
+ ));
+ })()}
+
+
+
+ {(() => {
+ const specialFeatures: { [key: string]: string } = {
+ indoor: 'Indoor Cat',
+ lap: 'Lap Cat',
+ hypoallergenic: 'Hypoallergenic',
+ hairless: 'Hairless',
+ rex: 'Rex Coat',
+ natural: 'Natural Breed',
+ rare: 'Rare Breed',
+ experimental: 'Experimental',
+ suppressed_tail: 'Suppressed Tail',
+ short_legs: 'Short Legs',
+ };
+ const enabledFeatures = Object.entries(specialFeatures)
+ .filter(([key]) => getBreedNumberProp(breed, key) === 1)
+ .map(([key, label]) => (
+
{label}
+ ));
+ return (
+
+
Special Features
+
+ {enabledFeatures.length > 0 ? enabledFeatures : None }
+
+
+ );
+ })()}
+
+ );
+};
+
+export default BreedDetails;
diff --git a/src/components/common/FavoriteBtn/FavoriteBtn.module.scss b/src/components/common/FavoriteBtn/FavoriteBtn.module.scss
new file mode 100644
index 00000000..70cd070d
--- /dev/null
+++ b/src/components/common/FavoriteBtn/FavoriteBtn.module.scss
@@ -0,0 +1,15 @@
+@use '../../../styles/variables' as *;
+
+.heartIcon {
+ path {
+ stroke: #000;
+ fill: white;
+ }
+
+ &.favorited {
+ path {
+ stroke: $color-red;
+ fill: $color-red;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/common/FavoriteBtn/FavoriteBtn.tsx b/src/components/common/FavoriteBtn/FavoriteBtn.tsx
new file mode 100644
index 00000000..38325230
--- /dev/null
+++ b/src/components/common/FavoriteBtn/FavoriteBtn.tsx
@@ -0,0 +1,82 @@
+import { Heart } from '@assets/icons/icons';
+import styles from './FavoriteBtn.module.scss';
+import { useAddToFavorites, useRemoveFromFavorites, useFavorites } from '@api/hooks';
+import { showGlobalToast } from '@hooks/useToast';
+import type { FavoriteBtnProps, FavoriteItem } from '@types';
+
+
+const FavoriteBtn = ({
+ imageId,
+ className = '',
+ title,
+ disabled = false
+}: FavoriteBtnProps) => {
+ const addToFavoritesMutation = useAddToFavorites();
+ const removeFromFavoritesMutation = useRemoveFromFavorites();
+ const { data: favorites } = useFavorites({
+ sub_id: 'user-gwi'
+ });
+
+ const isFavorited = Array.isArray(favorites) ?
+ favorites.some((fav: FavoriteItem) => fav.image_id === imageId) : false;
+
+ const handleFavoriteClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (disabled) return;
+
+ if (isFavorited && Array.isArray(favorites)) {
+ const favoriteItem = favorites.find((fav: FavoriteItem) => fav.image_id === imageId);
+ if (favoriteItem) {
+ removeFromFavoritesMutation.mutate(favoriteItem.id, {
+ onSuccess: () => {
+ showGlobalToast({
+ message: 'Removed from favorites!',
+ color: 'info'
+ });
+ },
+ onError: () => {
+ showGlobalToast({
+ message: 'Failed to remove from favorites',
+ color: 'error'
+ });
+ }
+ });
+ }
+ } else {
+ addToFavoritesMutation.mutate({
+ imageId: imageId,
+ subId: 'user-gwi'
+ }, {
+ onSuccess: () => {
+ showGlobalToast({
+ message: 'Added to favorites!',
+ color: 'success'
+ });
+ },
+ onError: () => {
+ showGlobalToast({
+ message: 'Failed to add to favorites',
+ color: 'error'
+ });
+ }
+ });
+ }
+ };
+
+ const isLoading = addToFavoritesMutation.isPending || removeFromFavoritesMutation.isPending;
+
+ return (
+
+
+
+ );
+};
+
+export default FavoriteBtn;
diff --git a/src/components/common/PageHeading/PageHeading.module.scss b/src/components/common/PageHeading/PageHeading.module.scss
new file mode 100644
index 00000000..e6327104
--- /dev/null
+++ b/src/components/common/PageHeading/PageHeading.module.scss
@@ -0,0 +1,20 @@
+@use '../../../styles/variables' as *;
+
+.headerContent {
+ text-align: center;
+ color: $color-white;
+ margin-top: 128px;
+ margin-bottom: $spacing-xl;
+}
+
+.title {
+ margin: 0 0 $spacing-md 0;
+ font-size: $font-size-xxl;
+ font-weight: bold;
+}
+
+.description {
+ margin: 0.5rem 0;
+ font-size: $font-size-md;
+ line-height: 1.4;
+}
\ No newline at end of file
diff --git a/src/components/common/PageHeading/PageHeading.tsx b/src/components/common/PageHeading/PageHeading.tsx
new file mode 100644
index 00000000..7780eaf4
--- /dev/null
+++ b/src/components/common/PageHeading/PageHeading.tsx
@@ -0,0 +1,15 @@
+import { useAppSelector } from '@store/hooks';
+import styles from './PageHeading.module.scss';
+
+const PageHeading = () => {
+ const { title, description } = useAppSelector(state => state.header);
+
+ return (
+
+
{title}
+ {description &&
{description}
}
+
+ )
+}
+
+export default PageHeading
\ No newline at end of file
diff --git a/src/components/common/ProgressBar/ProgressBar.module.scss b/src/components/common/ProgressBar/ProgressBar.module.scss
new file mode 100644
index 00000000..2a43dc09
--- /dev/null
+++ b/src/components/common/ProgressBar/ProgressBar.module.scss
@@ -0,0 +1,35 @@
+.progressContainer {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ z-index: 9999;
+ transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
+ display: flex;
+ justify-content: center;
+}
+
+.visible {
+ opacity: 1;
+ visibility: visible;
+}
+
+.hidden {
+ opacity: 0;
+ visibility: hidden;
+}
+
+.loader {
+ height: 4px;
+ width: 100%;
+ --c:no-repeat linear-gradient(#6100ee 0 0);
+ background: var(--c),var(--c),#d7b8fc;
+ background-size: 60% 100%;
+ animation: l16 3s infinite;
+}
+
+@keyframes l16 {
+ 0% {background-position:-150% 0,-150% 0}
+ 66% {background-position: 250% 0,-150% 0}
+ 100% {background-position: 250% 0, 250% 0}
+}
\ No newline at end of file
diff --git a/src/components/common/ProgressBar/ProgressBar.tsx b/src/components/common/ProgressBar/ProgressBar.tsx
new file mode 100644
index 00000000..9fd787bd
--- /dev/null
+++ b/src/components/common/ProgressBar/ProgressBar.tsx
@@ -0,0 +1,14 @@
+import { useLoading } from '@store/hooks';
+import styles from './ProgressBar.module.scss';
+
+const ProgressBar = () => {
+ const isLoading = useLoading();
+
+ return (
+
+ );
+};
+
+export default ProgressBar;
diff --git a/src/components/common/Rating/Rating.module.scss b/src/components/common/Rating/Rating.module.scss
new file mode 100644
index 00000000..20c5c2d4
--- /dev/null
+++ b/src/components/common/Rating/Rating.module.scss
@@ -0,0 +1,22 @@
+@use '../../../styles/variables' as *;
+
+.rating {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+
+ &__star {
+ &--filled {
+ fill: $color-text;
+ stroke: none;
+ }
+
+ &--empty {
+ stroke: $color-text;
+ stroke-width: 1px;
+ path {
+ fill:transparent;
+ }
+ }
+ }
+}
diff --git a/src/components/common/Rating/Rating.tsx b/src/components/common/Rating/Rating.tsx
new file mode 100644
index 00000000..393da0cb
--- /dev/null
+++ b/src/components/common/Rating/Rating.tsx
@@ -0,0 +1,44 @@
+import { Star } from '@assets/icons/icons';
+import styles from './Rating.module.scss';
+
+interface RatingProps {
+ rating: number;
+ maxRating?: number;
+ className?: string;
+}
+
+const Rating = ({ rating, maxRating = 5, className = '' }: RatingProps) => {
+ const stars = [];
+
+ for (let i = 1; i <= maxRating; i++) {
+ if (i <= rating) {
+ // Filled star
+ stars.push(
+
+ );
+ } else {
+ // Empty star
+ stars.push(
+
+ );
+ }
+ }
+
+ return (
+
+ {stars}
+
+ );
+};
+
+export default Rating;
diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx
new file mode 100644
index 00000000..c9610817
--- /dev/null
+++ b/src/components/layout/AppLayout.tsx
@@ -0,0 +1,25 @@
+import { Outlet } from 'react-router-dom';
+import Header from './Header/Header';
+import Footer from './Footer/Footer';
+import ProgressBar from '@components/common/ProgressBar/ProgressBar';
+import PageHeading from '@components/common/PageHeading/PageHeading';
+import { useAppSelector } from '@store/hooks';
+
+const AppLayout = () => {
+ const headerTitle = useAppSelector((state) => state.header.title);
+ document.title = `Meowbase | ${headerTitle}`;
+
+ return (
+
+ );
+}
+
+export default AppLayout
\ No newline at end of file
diff --git a/src/components/layout/Footer/Footer.module.scss b/src/components/layout/Footer/Footer.module.scss
new file mode 100644
index 00000000..526c1330
--- /dev/null
+++ b/src/components/layout/Footer/Footer.module.scss
@@ -0,0 +1,97 @@
+@use '../../../styles/variables' as *;
+
+.footer {
+ background: $color-background-dark;
+ padding: 1.5rem 0;
+ margin-top: auto;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 $spacing-md;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.leftSection {
+ display: flex;
+ align-items: center;
+}
+
+.logo {
+ transition: opacity 0.2s ease;
+ cursor: pointer;
+
+ &:hover {
+ :global(.cat-logo) {
+ transition: transform 0.3s ease;
+ transform: translateY(-40px);
+ }
+ }
+}
+
+.rightSection {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: $spacing-sm;
+}
+
+.contactTitle {
+ font-size: $font-size-md;
+ font-weight: 600;
+ color: $color-white;
+ margin-bottom: $spacing-xs;
+}
+
+.contactLinks {
+ display: flex;
+ gap: $spacing-md;
+}
+
+.contactLink {
+ display: flex;
+ align-items: center;
+ gap: $spacing-sm;
+ text-decoration: none;
+ color: $color-white;
+ padding: $spacing-sm;
+ border-radius: $radius-sm;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+ color: $color-secondary;
+ transform: translateY(-1px);
+ }
+}
+
+.icon {
+ width: 20px;
+ height: 20px;
+ fill: currentColor;
+ transition: fill 0.2s ease;
+}
+
+.linkText {
+ font-size: $font-size-sm;
+ font-weight: 500;
+}
+
+@media (max-width: 768px) {
+ .container {
+ flex-direction: column;
+ gap: $spacing-md;
+ text-align: center;
+ }
+
+ .rightSection {
+ align-items: center;
+ }
+
+ .contactLinks {
+ justify-content: center;
+ }
+}
\ No newline at end of file
diff --git a/src/components/layout/Footer/Footer.tsx b/src/components/layout/Footer/Footer.tsx
new file mode 100644
index 00000000..07b7ba56
--- /dev/null
+++ b/src/components/layout/Footer/Footer.tsx
@@ -0,0 +1,57 @@
+import styles from './Footer.module.scss';
+import { Logo, Linkedin, Gmail, Github } from '@assets/icons/icons';
+
+const CONTACTLINKS = [
+ {
+ href: 'https://www.linkedin.com/in/stefanos-stamoulis/',
+ label: 'LinkedIn',
+ icon: Linkedin
+ },
+ {
+ href: 'mailto:sstamoulis.wd@gmail.com',
+ label: 'Gmail',
+ icon: Gmail
+ },
+ {
+ href: 'https://github.com/StefanosSt',
+ label: 'GitHub',
+ icon: Github
+ }
+ ];
+
+const Footer = () => {
+
+
+ return (
+
+
+
+
+
+
+
+
Contact Me
+
+ {CONTACTLINKS.map((link) => {
+ const IconComponent = link.icon;
+ return (
+
+
+ {link.label}
+
+ );
+ })}
+
+
+
+
+ );
+};
+
+export default Footer;
\ No newline at end of file
diff --git a/src/components/layout/Header/Header.module.scss b/src/components/layout/Header/Header.module.scss
new file mode 100644
index 00000000..5a106188
--- /dev/null
+++ b/src/components/layout/Header/Header.module.scss
@@ -0,0 +1,174 @@
+@use '../../../styles/variables' as *;
+@use '../../../styles/mixins' as *;
+
+.header {
+ width: 100%;
+}
+
+.navbar {
+ position: fixed;
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 11;
+ margin-top: $spacing-xl;
+ width: 90%;
+ max-width: 80rem;
+ background: rgba(0, 0, 0, 0.2);
+ backdrop-filter: blur(16px);
+ border-radius: $radius-xl;
+ padding: $spacing-sm $spacing-md;
+ box-shadow:
+ 0 10px 25px -5px rgba(0, 0, 0, 0.1),
+ 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.navbarContainer {
+ @include flex-center;
+ justify-content: space-between;
+ width: 100%;
+}
+
+.navbarBrand {
+ @include flex-center;
+ gap: $spacing-xl;
+}
+
+.navbarLogo {
+ @include flex-center;
+ gap: $spacing-sm;
+ text-decoration: none;
+ font-size: $font-size-lg;
+ transition: opacity 0.2s ease;
+
+ &:hover {
+ opacity: 0.8;
+
+ :global(.cat-logo) {
+ transition: transform 0.3s ease;
+ transform: translateY(-40px);
+ }
+ }
+}
+
+.navbarLogoText {
+ color: $color-primary;
+ font-weight: 700;
+}
+
+.navbarLinks {
+ @include flex-center;
+ gap: $spacing-md;
+}
+
+.navbarLinksDesktop {
+ display: none;
+
+ @media (min-width: 768px) {
+ display: flex;
+ }
+}
+
+.navbarLink {
+ text-decoration: none;
+ color: rgba(255, 255, 255, 0.8);
+ font-weight: 500;
+ font-size: $font-size-sm;
+ padding: $spacing-sm $spacing-md;
+ border-radius: $radius-lg;
+ transition: all 0.2s ease;
+ position: relative;
+
+ &:hover {
+ color: $color-white;
+ background: rgba(255, 255, 255, 0.1);
+ }
+}
+
+.navbarLinkActive {
+ color: $color-primary;
+ background: rgba(255, 152, 0, 0.1);
+
+ &:hover {
+ background: rgba(255, 152, 0, 0.2);
+ }
+}
+
+.navbarToggle {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ width: 2.5rem;
+ height: 2.5rem;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ z-index: 10;
+
+ @media (min-width: 768px) {
+ display: none;
+ }
+}
+
+.navbarToggleIcon {
+ fill: $color-white;
+ border-radius: $radius-sm;
+ transition: all 0.3s linear;
+ position: relative;
+ transform-origin: 1px;
+}
+
+.navbarMobile {
+ @include flex-center;
+ flex-direction: column;
+ gap: $spacing-sm;
+ padding: $spacing-md;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+ margin-top: $spacing-sm;
+
+ @media (min-width: 768px) {
+ display: none;
+ }
+}
+
+.navbarMobileLink {
+ text-decoration: none;
+ color: rgba(255, 255, 255, 0.8);
+ font-weight: 500;
+ font-size: $font-size-md;
+ padding: $spacing-sm $spacing-md;
+ border-radius: $radius-lg;
+ transition: all 0.2s ease;
+ width: 100%;
+ text-align: center;
+
+ &:hover {
+ color: $color-white;
+ background: rgba(255, 255, 255, 0.1);
+ }
+}
+
+.navbarMobileLinkActive {
+ color: $color-primary;
+ background: rgba(255, 152, 0, 0.1);
+
+ &:hover {
+ background: rgba(255, 152, 0, 0.2);
+ }
+}
+
+.count {
+ position: absolute;
+ top: 0;
+ background-color: $color-red;
+ border-radius: 50%;
+ padding: 1px;
+ color: $color-text;
+ width: 16px;
+ height: 16px;
+ line-height: 1;
+ right: 0;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/src/components/layout/Header/Header.test.tsx b/src/components/layout/Header/Header.test.tsx
new file mode 100644
index 00000000..b7bfebb6
--- /dev/null
+++ b/src/components/layout/Header/Header.test.tsx
@@ -0,0 +1,72 @@
+// Header.test.tsx
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { MemoryRouter } from 'react-router-dom';
+import Header from './Header';
+
+// Mock store selector
+vi.mock('@store/hooks', () => ({
+ useAppSelector: () => ({ count: 2 }), // fake favorites count
+}));
+
+// Mock icons
+vi.mock('@assets/icons/icons', () => ({
+ Logo: () => Logo
,
+ HamburgerMenu: (props: React.SVGProps) => ,
+}));
+
+describe('Header', () => {
+ it('renders logo and brand text', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId('logo')).toBeInTheDocument();
+ expect(screen.getByText(/Meowbase/i)).toBeInTheDocument();
+ });
+
+ it('renders desktop navigation links', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByText(/Cats/i)).toBeInTheDocument();
+ expect(screen.getByText(/Breeds/i)).toBeInTheDocument();
+ expect(screen.getByText(/Favorites/i)).toBeInTheDocument();
+ });
+
+ it('shows favorites count when greater than 0', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByText('2')).toBeInTheDocument();
+ });
+
+ it('opens and closes mobile menu when hamburger is clicked', () => {
+ render(
+
+
+
+ );
+
+ // Mobile menu should NOT be visible initially
+ expect(screen.queryByRole('navigation', { hidden: true })).toBeInTheDocument();
+ expect(screen.queryByText(/Cats/i, { selector: 'a.' + 'navbarMobileLink' })).not.toBeInTheDocument();
+
+ // Open menu
+ fireEvent.click(screen.getByRole('button', { name: /toggle navigation/i }));
+ expect(screen.getAllByRole('link', { name: /Cats/i })).toHaveLength(2);
+
+ // Close menu
+ fireEvent.click(screen.getByRole('button', { name: /toggle navigation/i }));
+ // Only the desktop link should remain
+ expect(screen.getAllByRole('link', { name: /Cats/i })).toHaveLength(1);
+ });
+});
diff --git a/src/components/layout/Header/Header.tsx b/src/components/layout/Header/Header.tsx
new file mode 100644
index 00000000..8937ae94
--- /dev/null
+++ b/src/components/layout/Header/Header.tsx
@@ -0,0 +1,85 @@
+import { NavLink } from 'react-router-dom';
+import { useState } from 'react';
+import { Logo, HamburgerMenu } from '@assets/icons/icons';
+import { useClickOutside, useEscapeKey } from '@hooks';
+import { useAppSelector } from '@store/hooks';
+import styles from './Header.module.scss';
+import type { NavigationItem } from '@types';
+
+const NAVIGATION_ITEMS: NavigationItem[] = [
+ { href: '/', title: 'Cats' },
+ { href: '/breeds', title: 'Breeds' },
+ { href: '/favorites', title: 'Favorites' },
+];
+
+const Header = () => {
+ const [isOpen, setIsOpen] = useState(false);
+ const { count } = useAppSelector(state => state.favoritesCounter);
+ const navRef = useClickOutside(() => setIsOpen(false));
+ useEscapeKey(() => setIsOpen(false), isOpen);
+
+ return (
+
+ );
+};
+
+export default Header;
\ No newline at end of file
diff --git a/src/components/ui/BreedCard/BreedCard.module.scss b/src/components/ui/BreedCard/BreedCard.module.scss
new file mode 100644
index 00000000..149ea76c
--- /dev/null
+++ b/src/components/ui/BreedCard/BreedCard.module.scss
@@ -0,0 +1,49 @@
+@use '../../../styles/variables' as *;
+
+.card {
+ padding: $spacing-md;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: $radius-sm;
+ cursor:pointer;
+}
+
+.imageContainer {
+ margin-bottom: $spacing-sm;
+}
+
+.image {
+ width: 100%;
+ height: 200px;
+ object-fit: cover;
+ border-radius: $radius-sm;
+}
+
+.title {
+ color: $color-white;
+ margin-bottom: $spacing-sm;
+ font-size: $font-size-lg;
+}
+
+.origin {
+ color: $color-white-70;
+ font-size: $font-size-md;
+ margin-bottom: $spacing-sm;
+}
+
+.temperament {
+ color: $color-white-80;
+ font-size: $font-size-sm;
+ margin-bottom: $spacing-sm;
+}
+
+.lifeSpan {
+ color: $color-white-70;
+ font-size: $font-size-sm;
+ margin-bottom: $spacing-sm;
+}
+
+.description {
+ color: $color-white-60;
+ font-size: $font-size-sm;
+ line-height: 1.4;
+}
diff --git a/src/components/ui/BreedCard/BreedCard.test.tsx b/src/components/ui/BreedCard/BreedCard.test.tsx
new file mode 100644
index 00000000..c3b325cf
--- /dev/null
+++ b/src/components/ui/BreedCard/BreedCard.test.tsx
@@ -0,0 +1,71 @@
+// BreedCard.test.tsx
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen, fireEvent } from '@testing-library/react';
+import BreedCard from './BreedCard';
+
+// Mock cat placeholder import
+vi.mock('@assets/cat.png', () => ({ default: 'placeholder.png' }));
+
+describe('BreedCard', () => {
+ const baseProps = {
+ id: 'breed123',
+ name: 'Persian',
+ description: 'Calm and affectionate cat.',
+ temperament: 'Gentle, Quiet',
+ origin: 'Iran',
+ lifeSpan: '12-17',
+ image: { id: 'img123', url: 'http://example.com/persian.jpg' },
+ onClick: vi.fn(),
+ };
+
+ it('renders the image with correct src and alt', () => {
+ render( );
+ const img = screen.getByRole('img', { name: /Persian/i });
+
+ expect(img).toBeInTheDocument();
+ expect(img).toHaveAttribute('src', baseProps.image.url);
+ expect(img).toHaveAttribute('alt', baseProps.name);
+ });
+
+ it('falls back to placeholder when no image is provided', () => {
+ const { id, name, description, temperament, origin, lifeSpan, onClick } = baseProps;
+ render(
+
+ );
+ const img = screen.getByRole('img', { name: /Persian/i });
+
+ expect(img).toHaveAttribute('src', 'placeholder.png');
+ });
+
+ it('renders all breed info fields', () => {
+ render( );
+ expect(screen.getByText('Persian')).toBeInTheDocument();
+ expect(screen.getByText(/Origin: Iran/i)).toBeInTheDocument();
+ expect(screen.getByText(/Temperament: Gentle, Quiet/i)).toBeInTheDocument();
+ expect(screen.getByText(/Life Span: 12-17 years/i)).toBeInTheDocument();
+ expect(screen.getByText(/Calm and affectionate cat./i)).toBeInTheDocument();
+ });
+
+ it('truncates long descriptions', () => {
+ const longDesc = 'x'.repeat(150); // 150 chars
+ render( );
+ expect(screen.getByText(/x{120}\.\.\./)).toBeInTheDocument();
+ });
+
+ it('calls onClick when the card is clicked', () => {
+ render( );
+ const card = screen.getByRole('button');
+ fireEvent.click(card);
+
+ expect(baseProps.onClick).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/components/ui/BreedCard/BreedCard.tsx b/src/components/ui/BreedCard/BreedCard.tsx
new file mode 100644
index 00000000..f73905e9
--- /dev/null
+++ b/src/components/ui/BreedCard/BreedCard.tsx
@@ -0,0 +1,55 @@
+import styles from './BreedCard.module.scss';
+import type { BreedCardProps } from '@types';
+import catPlaceholder from '@assets/cat.png';
+
+const BreedCard = ({
+ id,
+ name,
+ description,
+ temperament,
+ origin,
+ lifeSpan,
+ image,
+ onClick,
+}: BreedCardProps) => {
+ return (
+
+
+
+
+
+ {name}
+
+ {origin && (
+
+ Origin: {origin}
+
+ )}
+ {temperament && (
+
+ Temperament: {temperament}
+
+ )}
+ {lifeSpan && (
+
+ Life Span: {lifeSpan} years
+
+ )}
+ {description && (
+
+ {description.length > 120
+ ? `${description.substring(0, 120)}...`
+ : description
+ }
+
+ )}
+
+ );
+};
+
+export default BreedCard;
diff --git a/src/components/ui/CatCard/CatCard.module.scss b/src/components/ui/CatCard/CatCard.module.scss
new file mode 100644
index 00000000..65e14aee
--- /dev/null
+++ b/src/components/ui/CatCard/CatCard.module.scss
@@ -0,0 +1,63 @@
+@use '../../../styles/variables' as *;
+
+.card {
+ border: 1px solid $color-border-light;
+ position: relative;
+ border-radius: $radius-sm;
+ overflow: hidden;
+ background: $color-background-light;
+ grid-column: 1 / -1;
+ cursor: pointer;
+ transition: transform 0.2s ease-in-out;
+
+ &:hover {
+ transform: scale(1.02);
+ }
+
+ @media (min-width: 768px) {
+ grid-column: auto;
+ }
+}
+
+.imageContainer {
+ width: 100%;
+ height: 250px;
+ overflow: hidden;
+}
+
+.image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.content {
+ padding: $spacing-md;
+}
+
+.id {
+ margin: 0 0 $spacing-sm 0;
+ font-weight: bold;
+ color: $color-grey;
+}
+
+.dimensions {
+ margin: 0;
+ font-size: $font-size-sm;
+ color: $color-grey;
+}
+
+.cardFavoriteBtn {
+ background-color: transparent;
+ border: none;
+ cursor: pointer;
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ transition: transform 0.2s ease-in-out;
+ z-index: 10;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+}
\ No newline at end of file
diff --git a/src/components/ui/CatCard/CatCard.test.tsx b/src/components/ui/CatCard/CatCard.test.tsx
new file mode 100644
index 00000000..a710ea24
--- /dev/null
+++ b/src/components/ui/CatCard/CatCard.test.tsx
@@ -0,0 +1,48 @@
+// CatCard.test.tsx
+import { describe, it, expect, vi } from 'vitest';
+import { render, screen, fireEvent } from '@testing-library/react';
+import CatCard from './CatCard';
+
+// Mock FavoriteBtn
+vi.mock('@components/common/FavoriteBtn/FavoriteBtn', () => ({
+ default: ({ imageId, className }: { imageId: string; className: string }) => (
+
+ FavoriteBtn
+
+ ),
+}));
+
+describe('CatCard', () => {
+ const mockProps = {
+ id: 'cat123',
+ imageUrl: 'http://example.com/cat.jpg',
+ alt: 'The cutest cat of Meowbase',
+ openModal: vi.fn(),
+ };
+
+ it('renders the cat image with correct src and alt', () => {
+ render( );
+ const img = screen.getByRole('img', { name: /cutest cat/i });
+
+ expect(img).toBeInTheDocument();
+ expect(img).toHaveAttribute('src', mockProps.imageUrl);
+ expect(img).toHaveAttribute('alt', mockProps.alt);
+ });
+
+ it('renders FavoriteBtn with correct props', () => {
+ render( );
+ const favBtn = screen.getByTestId('favorite-btn');
+
+ expect(favBtn).toBeInTheDocument();
+ expect(favBtn).toHaveAttribute('data-id', mockProps.id);
+ });
+
+ it('calls openModal when the card is clicked', () => {
+ render( );
+ const card = screen.getByRole('button');
+
+ fireEvent.click(card);
+
+ expect(mockProps.openModal).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/components/ui/CatCard/CatCard.tsx b/src/components/ui/CatCard/CatCard.tsx
new file mode 100644
index 00000000..b40b5dea
--- /dev/null
+++ b/src/components/ui/CatCard/CatCard.tsx
@@ -0,0 +1,27 @@
+import FavoriteBtn from '@components/common/FavoriteBtn/FavoriteBtn';
+import styles from './CatCard.module.scss';
+import type { CatCardProps } from '@types';
+
+const CatCard = ({
+ id,
+ imageUrl,
+ alt = 'Cat image',
+ openModal
+}: CatCardProps) => {
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default CatCard;
\ No newline at end of file
diff --git a/src/components/ui/Filters/Filters.module.scss b/src/components/ui/Filters/Filters.module.scss
new file mode 100644
index 00000000..d9b38357
--- /dev/null
+++ b/src/components/ui/Filters/Filters.module.scss
@@ -0,0 +1,52 @@
+@use '../../../styles/variables' as *;
+
+.filters {
+ margin-bottom: $spacing-lg;
+ padding: $spacing-md;
+ background: linear-gradient(135deg, $color-background-light 0%, $color-background-light 100%);
+ border-radius: $radius-md;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ width: fit-content;
+}
+
+.radioGroup {
+ display: flex;
+ gap: $spacing-sm;
+}
+
+.radioGroup label {
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: $spacing-sm $spacing-lg;
+ background: $color-background-light;
+ border: 2px solid $color-border-light;
+ border-radius: $radius-lg;
+ cursor: pointer;
+ font-weight: 500;
+ color: $color-grey;
+ transition: all 0.3s ease;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+
+ &:hover {
+ border-color: $color-secondary;
+ color: $color-button;
+ transform: translateY(-1px);
+ box-shadow: 0 3px 8px rgba(0, 123, 255, 0.2);
+ }
+
+ &:has(input:checked) {
+ background: linear-gradient(135deg, $color-button 0%, $color-button-hover 100%);
+ border-color: $color-button-hover;
+ color: $color-white;
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
+ }
+}
+
+.radioGroup input[type="radio"] {
+ position: absolute;
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
diff --git a/src/components/ui/Filters/Filters.tsx b/src/components/ui/Filters/Filters.tsx
new file mode 100644
index 00000000..77463058
--- /dev/null
+++ b/src/components/ui/Filters/Filters.tsx
@@ -0,0 +1,33 @@
+import { memo } from 'react';
+import styles from './Filters.module.scss';
+import type { FiltersProps } from '@types'
+
+
+const Filters = memo(({ hasBreeds, onChange }: FiltersProps) => {
+ return (
+
+ );
+});
+
+export default Filters;
diff --git a/src/components/ui/GridLayout/GridLayout.module.scss b/src/components/ui/GridLayout/GridLayout.module.scss
new file mode 100644
index 00000000..1e5f819a
--- /dev/null
+++ b/src/components/ui/GridLayout/GridLayout.module.scss
@@ -0,0 +1,22 @@
+.grid {
+ display: grid;
+ width: 100%;
+ grid-auto-flow: dense;
+ row-gap: var(--grid-gap, 1rem);
+ grid-template-columns: 1fr;
+
+ @media (min-width: 768px) {
+ grid-template-columns: repeat(2, 1fr);
+ gap: var(--grid-gap, 1rem);
+ grid-auto-flow: dense;
+
+ > * {
+ grid-column: auto;
+ }
+ }
+
+ @media (min-width: 1024px) {
+ grid-template-columns: repeat(var(--grid-columns, 4), 1fr);
+ grid-auto-flow: dense;
+ }
+}
\ No newline at end of file
diff --git a/src/components/ui/GridLayout/GridLayout.tsx b/src/components/ui/GridLayout/GridLayout.tsx
new file mode 100644
index 00000000..dfc4f2bd
--- /dev/null
+++ b/src/components/ui/GridLayout/GridLayout.tsx
@@ -0,0 +1,25 @@
+import styles from './GridLayout.module.scss';
+import type { GridProps } from '@types';
+
+const GridLayout = ({
+ children,
+ columns = 1,
+ gap = '1rem',
+ className,
+}: GridProps) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default GridLayout;
diff --git a/src/components/ui/Modal/Modal.module.scss b/src/components/ui/Modal/Modal.module.scss
new file mode 100644
index 00000000..e8f75e9c
--- /dev/null
+++ b/src/components/ui/Modal/Modal.module.scss
@@ -0,0 +1,192 @@
+@use '../../../styles/variables' as *;
+
+.modal {
+ border: none;
+ border-radius: $radius-sm;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ top: 100%;
+ left: 50%;
+ transform: translate(-50%, -100%);
+ min-width:100vw;
+ height:90vh;
+
+ &::backdrop {
+ background-color: rgba(0, 0, 0, 0.5);
+ }
+
+ img {
+ max-height: 600px;
+ width:100%;
+ object-fit: cover;
+ border-radius:$radius-md;
+ }
+
+ @media(min-width:767px) {
+ top: 50%;
+ transform: translate(-50%, -50%);
+ width: 800px;
+ min-width:unset;
+ height:80vh;
+ }
+}
+
+.modalContent {
+ padding: 0;
+ background: white;
+ border-radius: $radius-sm;
+ min-width: 300px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.modalHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: $spacing-md;
+ border-bottom: 1px solid $color-border-light;
+ position: sticky;
+ top: 0;
+ background: $color-white;
+ z-index: 10;
+ flex-shrink: 0;
+}
+
+.modalTitle {
+ margin: 0;
+ font-size: $font-size-xl;
+ font-weight: 600;
+}
+
+.closeButton {
+ background: none;
+ border: none;
+ font-size: $font-size-lg;
+ cursor: pointer;
+ padding: $spacing-xs;
+ line-height: 1;
+ color: $color-grey;
+
+ &:hover {
+ color: $color-dark;
+ }
+}
+
+.backButton {
+ background: none;
+ border: none;
+ cursor:pointer;
+
+ &:hover {
+ color: $color-dark;
+ }
+}
+
+.modalBody {
+ padding: $spacing-sm;
+ overflow-y: auto;
+ flex: 1;
+ height: 0;
+}
+
+// ModalContent styles
+.modal__details {
+ display: flex;
+ justify-content:space-between;
+ gap: $spacing-sm;
+}
+
+.modal__details-info {
+ p {
+ margin: 0.5rem 0;
+ font-size: $font-size-md;
+
+ strong {
+ color: $color-primary;
+ margin-right: $spacing-sm;
+ }
+ }
+}
+
+.modal__details-actions {
+
+ button {
+ background-color: transparent;
+ border: none;
+ padding: $spacing-sm;
+ cursor: pointer;
+ border-radius: $radius-xs;
+ svg {
+ transition: fill 0.2s ease;
+ }
+
+ &:hover {
+ svg{
+ fill:$color-primary;
+ }
+ }
+
+ }
+}
+
+.modal__no-breed {
+ padding: $spacing-lg;
+ text-align: center;
+ background-color: $color-background-light;
+ border-radius: $radius-sm;
+ margin-top: $spacing-md;
+ border: 1px solid $color-border-light;
+
+ p {
+ margin: 0;
+ font-size: $font-size-md;
+ color: $color-grey;
+ font-style: italic;
+ }
+}
+
+.modal__breed-images {
+ margin-top: $spacing-lg;
+}
+
+.modal__breed-gallery {
+ display: flex;
+ flex-wrap: wrap;
+ gap: $spacing-md;
+ justify-content: center;
+}
+
+.modal__breed-circle {
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ overflow: hidden;
+ border: 3px solid $color-border-light;
+ transition: transform 0.2s ease, border-color 0.2s ease;
+ cursor: pointer;
+
+ &:hover {
+ transform: scale(1.1);
+ border-color: $color-primary;
+ }
+}
+
+.modal__breed-image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 50%;
+}
+
+.modal__loading {
+ text-align: center;
+ padding: $spacing-lg;
+
+ p {
+ margin: 0;
+ font-size: $font-size-md;
+ color: $color-grey;
+ font-style: italic;
+ }
+}
\ No newline at end of file
diff --git a/src/components/ui/Modal/Modal.test.tsx b/src/components/ui/Modal/Modal.test.tsx
new file mode 100644
index 00000000..61a3dc23
--- /dev/null
+++ b/src/components/ui/Modal/Modal.test.tsx
@@ -0,0 +1,93 @@
+// Modal.test.tsx
+import React from 'react';
+import { describe, it, expect, vi, beforeEach, beforeAll } from 'vitest';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { Modal } from './Modal';
+
+// --- Mock dialog methods (jsdom doesnβt implement these) ---
+beforeAll(() => {
+ window.HTMLDialogElement.prototype.showModal = vi.fn();
+ window.HTMLDialogElement.prototype.close = vi.fn();
+});
+
+// Mock hooks
+vi.mock('@hooks', () => ({
+ useLockBodyScroll: vi.fn(),
+ useEscapeKey: vi.fn(),
+}));
+
+// Mock redux hooks
+const mockDispatch = vi.fn();
+let mockCurrentView = 'cat';
+vi.mock('@store/hooks', () => ({
+ useAppDispatch: () => mockDispatch,
+ useAppSelector: (selector: (state: unknown) => unknown) =>
+ selector({ modalContent: { currentView: mockCurrentView } }),
+}));
+
+// Mock slice action
+vi.mock('@store/modalContentSlice', () => ({
+ switchToBreedView: () => ({ type: 'modal/switchToBreedView' }),
+}));
+
+// Mock icons
+vi.mock('@assets/icons/icons', () => ({
+ Arrow: () => β ,
+}));
+
+describe('Modal', () => {
+ let defaultProps: {
+ isOpen: boolean;
+ setIsOpen: () => void;
+ onClose: () => void;
+ children: React.ReactNode;
+ title: string;
+ orientation: string;
+ catId: string;
+ };
+
+ beforeEach(() => {
+ defaultProps = {
+ isOpen: true,
+ setIsOpen: vi.fn(),
+ onClose: vi.fn(),
+ children: Modal content
,
+ title: 'Test Modal',
+ orientation: 'center',
+ catId: 'cat123',
+ };
+ mockDispatch.mockClear();
+ mockCurrentView = 'cat';
+ });
+
+ it('renders title and children when open', () => {
+ render( );
+ expect(screen.getByText('Test Modal')).toBeInTheDocument();
+ expect(screen.getByText('Modal content')).toBeInTheDocument();
+ });
+
+ it('calls onClose when close button is clicked', () => {
+ render( );
+ fireEvent.click(screen.getByTestId('modal-close-btn'));
+ expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls onClose when clicking backdrop', () => {
+ render( );
+ const dialog = screen.getByTestId('modal-dialog');
+ fireEvent.click(dialog, { target: dialog, currentTarget: dialog });
+ expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
+ });
+
+ it('dispatches switchToBreedView when back button is clicked (if currentView is "cat")', () => {
+ render( );
+ fireEvent.click(screen.getByTestId('modal-back-btn'));
+ expect(mockDispatch).toHaveBeenCalledWith({ type: 'modal/switchToBreedView' });
+ });
+
+ it('does not render back button if currentView is not "cat"', () => {
+ mockCurrentView = 'breed';
+ render( );
+ expect(screen.queryByRole('button', { name: /go back/i })).not.toBeInTheDocument();
+ });
+});
diff --git a/src/components/ui/Modal/Modal.tsx b/src/components/ui/Modal/Modal.tsx
new file mode 100644
index 00000000..02399f62
--- /dev/null
+++ b/src/components/ui/Modal/Modal.tsx
@@ -0,0 +1,76 @@
+import { useRef, useEffect } from 'react';
+import { useLockBodyScroll, useEscapeKey } from '@hooks';
+import { useAppDispatch, useAppSelector } from '@store/hooks';
+import { switchToBreedView } from '@store/modalContentSlice';
+import styles from './Modal.module.scss';
+import { Arrow } from '@assets/icons/icons';
+import type { ModalProps } from '@types';
+
+export const Modal = ({ isOpen, setIsOpen, onClose, children, title, orientation, catId }: ModalProps) => {
+ const dialogRef = useRef(null);
+ useLockBodyScroll(isOpen);
+ useEscapeKey(() => setIsOpen(false), isOpen);
+ const dispatch = useAppDispatch();
+
+ const { currentView } = useAppSelector(state => state.modalContent);
+
+ useEffect(() => {
+ const dialog = dialogRef.current;
+ if (!dialog) return;
+
+ if (isOpen) {
+ dialog.showModal();
+ } else {
+ dialog.close();
+ }
+ }, [isOpen, catId]);
+
+ const handleBackClick = () => {
+ dispatch(switchToBreedView());
+ };
+
+ const handleBackdropClick = (e: React.MouseEvent) => {
+ if (e.target === e.currentTarget) {
+ onClose();
+ }
+ };
+
+ return (
+
+
+
+ {currentView === 'cat' && (
+
+
+
+ )}
+ {title && {title} }
+
+ Γ
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/components/ui/Modal/ModalContent.tsx b/src/components/ui/Modal/ModalContent.tsx
new file mode 100644
index 00000000..999694ed
--- /dev/null
+++ b/src/components/ui/Modal/ModalContent.tsx
@@ -0,0 +1,150 @@
+import { useEffect, useRef, useState } from "react";
+import { useReactToPrint } from "react-to-print";
+import { Share, Download, Print } from '@assets/icons/icons';
+import BreedDetails from '@components/common/BreedDetails/BreedDetails';
+import FavoriteBtn from '@components/common/FavoriteBtn/FavoriteBtn';
+import { setBreedContent, setCatContent } from '@store/modalContentSlice';
+import { setSharedUrl } from '@store/sharedUrlSlice';
+import { showGlobalToast } from '@hooks/useToast';
+import { useAppDispatch, useAppSelector } from '@store/hooks';
+import { useCatById } from '@api/hooks';
+import styles from './Modal.module.scss';
+
+import type { ModalDetails, ModalContentProps } from '@types';
+
+const ModalContent = ({ content, hasBreedDetails = false, currentBreedCats }: ModalContentProps) => {
+ const [selectedImageId, setSelectedImageId] = useState(null);
+ const { currentView, catContent } = useAppSelector(state => state.modalContent);
+ const { currentUrl } = useAppSelector(state => state.sharedUrl);
+
+ const printRef = useRef(null);
+ const dispatch = useAppDispatch();
+
+ const { data: catDetails } = useCatById(
+ selectedImageId || '',
+ { enabled: !!selectedImageId }
+ ) as { data: ModalDetails | undefined };
+
+ useEffect(() => {
+ if (currentBreedCats && currentBreedCats.length > 0) {
+ dispatch(setBreedContent({
+ currentBreedCats,
+ breedData: content.breeds?.[0],
+
+ }));
+ }
+ }, [currentBreedCats, content.breeds, dispatch]);
+
+ useEffect(() => {
+ if (catDetails && selectedImageId) {
+ dispatch(setCatContent({
+ catDetails: {
+ id: catDetails.id,
+ url: catDetails.url,
+ width: catDetails.width,
+ height: catDetails.height,
+ breeds: catDetails.breeds
+ }
+ }));
+
+ setSelectedImageId(null);
+ dispatch(setSharedUrl(`?cat_id=${selectedImageId}`));
+ }
+ }, [catDetails, selectedImageId, dispatch, setSelectedImageId]);
+
+ const handlePrint = useReactToPrint({
+ contentRef: printRef,
+ });
+
+ const handleShareButton = () => {
+ const baseUrl = window.location.origin;
+ const fullUrl = currentUrl ? `${baseUrl}${currentUrl}` : window.location.href;
+
+ navigator.clipboard.writeText(fullUrl);
+ showGlobalToast({
+ message: 'Copied to clipboard',
+ color: 'info'
+ });
+ };
+
+ const extractImageIdFromUrl = (imageUrl: string): string => {
+ const urlParts = imageUrl.split('/');
+ const filename = urlParts[urlParts.length - 1];
+ return filename.split('.')[0];
+ };
+
+ const handleBreedImageClick = (imageUrl: string) => {
+ const imageId = extractImageIdFromUrl(imageUrl);
+ setSelectedImageId(imageId);
+ };
+
+ const displayDetails = currentView === 'cat' && catContent ? catContent.catDetails : content;
+ const breedInfo = content.breeds && content.breeds.length > 0 ? content.breeds[0] : content;
+
+ const orientation = displayDetails.width && displayDetails.height
+ ? displayDetails.width === displayDetails.height
+ ? 'Square'
+ : displayDetails.width > displayDetails.height
+ ? 'Landscape'
+ : 'Portrait'
+ : '';
+
+ return (
+ <>
+ {currentBreedCats && currentBreedCats.length > 0 && currentView === 'breed' ? (
+
+
Explore the {content.name || 'Breed'} cats
+
+ {currentBreedCats.map((imageUrl, index) => (
+
handleBreedImageClick(imageUrl)}
+ style={{ cursor: 'pointer' }}
+ >
+
+
+ ))}
+
+
+ ) : (
+ <>
+
+
+
+
+ {!!displayDetails.width && (
+
+
Dimensions: {displayDetails.width} Γ {displayDetails.height}px
+
Orientation: {orientation}
+
+ )}
+
+
+
+
window.open(displayDetails.url, "_blank")}>
+
+
+
+ >
+ )}
+
+ {hasBreedDetails ? (
+
+ ) : (
+
+
Unfortunately there is not any breed information for this cat
+
+ )}
+ >
+ );
+};
+
+export default ModalContent;
\ No newline at end of file
diff --git a/src/components/ui/Skeletons/BreedCardSkeleton.tsx b/src/components/ui/Skeletons/BreedCardSkeleton.tsx
new file mode 100644
index 00000000..d0d747a3
--- /dev/null
+++ b/src/components/ui/Skeletons/BreedCardSkeleton.tsx
@@ -0,0 +1,33 @@
+import ContentLoader from 'react-content-loader';
+import styles from './Skeletons.module.scss';
+
+export const BreedCardSkeleton = () => (
+
+
+ {/* Image area */}
+
+
+ {/* Title */}
+
+
+ {/* Origin */}
+
+
+ {/* Temperament */}
+
+
+ {/* Life Span */}
+
+
+ {/* Description lines */}
+
+
+
+);
+
diff --git a/src/components/ui/Skeletons/CatCardSkeleton.tsx b/src/components/ui/Skeletons/CatCardSkeleton.tsx
new file mode 100644
index 00000000..afd7f004
--- /dev/null
+++ b/src/components/ui/Skeletons/CatCardSkeleton.tsx
@@ -0,0 +1,16 @@
+import ContentLoader from 'react-content-loader';
+import styles from './Skeletons.module.scss';
+
+export const CatCardSkeleton = () => (
+
+
+
+
+
+);
diff --git a/src/components/ui/Skeletons/GridSkeleton.tsx b/src/components/ui/Skeletons/GridSkeleton.tsx
new file mode 100644
index 00000000..d97b171c
--- /dev/null
+++ b/src/components/ui/Skeletons/GridSkeleton.tsx
@@ -0,0 +1,27 @@
+import GridLayout from '@components/ui/GridLayout/GridLayout';
+import { CatCardSkeleton } from './CatCardSkeleton';
+import { BreedCardSkeleton } from './BreedCardSkeleton';
+import styles from './Skeletons.module.scss';
+import type { GridSkeletonProps } from '@types';
+
+export const GridSkeleton = ({
+ type = 'cat',
+ count = 8,
+ columns = 5,
+ gap = '1rem',
+ className = 'grid'
+}: GridSkeletonProps) => {
+ const SkeletonComponent = type === 'breed' ? BreedCardSkeleton : CatCardSkeleton;
+
+ return (
+
+
+ {Array.from({ length: count }, (_, index) => (
+
+
+
+ ))}
+
+
+ );
+};
diff --git a/src/components/ui/Skeletons/Skeletons.module.scss b/src/components/ui/Skeletons/Skeletons.module.scss
new file mode 100644
index 00000000..f266fb78
--- /dev/null
+++ b/src/components/ui/Skeletons/Skeletons.module.scss
@@ -0,0 +1,17 @@
+@use '../../../styles/variables' as *;
+
+// BreedCard Skeleton Styles
+.breedCardSkeleton {
+ padding: $spacing-md;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: $radius-md;
+ backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+
+
+.breedCardSkeleton {
+ background: rgba(255, 255, 255, 0.1);
+ border-color: rgba(255, 255, 255, 0.2);
+}
diff --git a/src/components/ui/Skeletons/Skeletons.tsx b/src/components/ui/Skeletons/Skeletons.tsx
new file mode 100644
index 00000000..15bcc343
--- /dev/null
+++ b/src/components/ui/Skeletons/Skeletons.tsx
@@ -0,0 +1,3 @@
+export { CatCardSkeleton } from './CatCardSkeleton';
+export { BreedCardSkeleton } from './BreedCardSkeleton';
+export { GridSkeleton } from './GridSkeleton';
\ No newline at end of file
diff --git a/src/components/ui/Toast/Toast.module.scss b/src/components/ui/Toast/Toast.module.scss
new file mode 100644
index 00000000..f5ab6bf6
--- /dev/null
+++ b/src/components/ui/Toast/Toast.module.scss
@@ -0,0 +1,59 @@
+@use '../../../styles/variables' as *;
+
+.toast {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ max-width: 300px;
+ padding: $spacing-sm $spacing-md;
+ border-radius: $radius-sm;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ z-index: 9999;
+ animation: slideIn 0.3s ease-out;
+ font-weight: 500;
+
+ &__message {
+ display: block;
+ font-size: $font-size-sm;
+ line-height: 1.4;
+ }
+
+ &--success {
+ background-color: $color-background-light;
+ color: $color-success-dark;
+ border-left: 4px solid $color-success-light;
+ }
+
+ &--error {
+ background-color: $color-error-light;
+ color: $color-error-dark;
+ border-left: 4px solid $color-error-dark;
+ }
+
+ &--info {
+ background-color: $color-info-light;
+ color: $color-info-dark;
+ border-left: 4px solid $color-info-dark;
+ }
+}
+
+@media (max-width: 768px) {
+ .toast {
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ max-width: none;
+ margin: 0 $spacing-sm;
+ }
+}
+
+@keyframes slideIn {
+ from {
+ transform: translateX(100%);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+}
diff --git a/src/components/ui/Toast/Toast.tsx b/src/components/ui/Toast/Toast.tsx
new file mode 100644
index 00000000..37362f1e
--- /dev/null
+++ b/src/components/ui/Toast/Toast.tsx
@@ -0,0 +1,27 @@
+import { useEffect } from 'react';
+import { useToast } from '@hooks/useToast';
+import styles from './Toast.module.scss';
+
+const Toast = () => {
+ const { toastData, hideToast } = useToast();
+
+ useEffect(() => {
+ if (toastData.show) {
+ const timer = setTimeout(() => {
+ hideToast();
+ }, 3000);
+
+ return () => clearTimeout(timer);
+ }
+ }, [toastData.show, hideToast]);
+
+ if (!toastData.show) return null;
+
+ return (
+
+ {toastData.message}
+
+ );
+};
+
+export default Toast;
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
new file mode 100644
index 00000000..da98e04e
--- /dev/null
+++ b/src/hooks/index.ts
@@ -0,0 +1,5 @@
+export { useClickOutside } from './useClickOutside';
+export { useEscapeKey } from './useEscapeKey';
+export { useLocalStorage } from './useLocalStorage';
+export { useToast, showGlobalToast } from './useToast';
+export { useLockBodyScroll } from './useLockBodyScroll';
\ No newline at end of file
diff --git a/src/hooks/useClickOutside.ts b/src/hooks/useClickOutside.ts
new file mode 100644
index 00000000..7262929c
--- /dev/null
+++ b/src/hooks/useClickOutside.ts
@@ -0,0 +1,21 @@
+import { useEffect, useRef } from 'react';
+
+export function useClickOutside(callback: () => void) {
+ const ref = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (ref.current && !ref.current.contains(event.target as Node | null)) {
+ callback();
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [callback]);
+
+ return ref;
+}
diff --git a/src/hooks/useEscapeKey.ts b/src/hooks/useEscapeKey.ts
new file mode 100644
index 00000000..bf944fce
--- /dev/null
+++ b/src/hooks/useEscapeKey.ts
@@ -0,0 +1,19 @@
+import { useEffect } from 'react';
+
+export function useEscapeKey(callback: () => void, isActive: boolean = true) {
+ useEffect(() => {
+ if (!isActive) return;
+
+ const handleEscapeKey = (event: KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ callback();
+ }
+ };
+
+ document.addEventListener('keydown', handleEscapeKey);
+
+ return () => {
+ document.removeEventListener('keydown', handleEscapeKey);
+ };
+ }, [callback, isActive]);
+}
diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts
new file mode 100644
index 00000000..3803902b
--- /dev/null
+++ b/src/hooks/useLocalStorage.ts
@@ -0,0 +1,15 @@
+import { useState } from 'react';
+
+export function useLocalStorage(key: string, initialValue: number) {
+ const [value, setValue] = useState(() => {
+ const item = window.localStorage.getItem(key);
+ return item ? JSON.parse(item) : initialValue;
+ });
+
+ const setStoredValue = (newValue: number) => {
+ setValue(newValue);
+ window.localStorage.setItem(key, JSON.stringify(newValue));
+ };
+
+ return [value, setStoredValue] as const;
+}
diff --git a/src/hooks/useLockBodyScroll.ts b/src/hooks/useLockBodyScroll.ts
new file mode 100644
index 00000000..75c8bb6c
--- /dev/null
+++ b/src/hooks/useLockBodyScroll.ts
@@ -0,0 +1,13 @@
+import { useEffect } from "react";
+
+export function useLockBodyScroll(isLocked: boolean = true) {
+ useEffect(() => {
+ if (isLocked) {
+ const prev = document.body.style.overflow;
+ document.body.style.overflow = "hidden";
+ return () => {
+ document.body.style.overflow = prev;
+ };
+ }
+ }, [isLocked]);
+}
\ No newline at end of file
diff --git a/src/hooks/useToast.ts b/src/hooks/useToast.ts
new file mode 100644
index 00000000..8ce68827
--- /dev/null
+++ b/src/hooks/useToast.ts
@@ -0,0 +1,44 @@
+import { useState, useCallback } from 'react';
+
+export interface ToastData {
+ message: string;
+ color: 'success' | 'error' | 'info';
+}
+
+let globalShowToast: ((data: ToastData) => void) | null = null;
+
+export const useToast = () => {
+ const [toastData, setToastData] = useState({
+ show: false,
+ message: '',
+ color: 'info',
+ });
+
+ const showToast = useCallback((data: ToastData) => {
+ setToastData({
+ ...data,
+ show: true,
+ });
+ }, []);
+
+ const hideToast = useCallback(() => {
+ setToastData(prev => ({ ...prev, show: false }));
+ }, []);
+
+ if (!globalShowToast) {
+ globalShowToast = showToast;
+ }
+
+ return {
+ toastData,
+ showToast,
+ hideToast,
+ };
+};
+
+// Global function to show toast from anywhere
+export const showGlobalToast = (data: ToastData) => {
+ if (globalShowToast) {
+ globalShowToast(data);
+ }
+};
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 00000000..46c5569e
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,18 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
+import { Provider } from 'react-redux';
+import { store } from '@store/store';
+import App from './App.tsx'
+
+const queryClient = new QueryClient()
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+
+
+
+ ,
+)
diff --git a/src/pages/Breeds.tsx b/src/pages/Breeds.tsx
new file mode 100644
index 00000000..df25c66a
--- /dev/null
+++ b/src/pages/Breeds.tsx
@@ -0,0 +1,129 @@
+import { useEffect, useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { setHeaderData } from '@store/headerSlice';
+import { useAppDispatch } from '@store/hooks';
+import { useBreeds, useCatsByBreed } from '@api/hooks';
+import { GridSkeleton } from '@/components/ui/Skeletons/Skeletons';
+import BreedCard from '@/components/ui/BreedCard/BreedCard';
+import GridLayout from '@/components/ui/GridLayout/GridLayout';
+import { Modal } from '@/components/ui/Modal/Modal';
+import ModalContent from '@/components/ui/Modal/ModalContent';
+import type { Breed } from '@types';
+import { showGlobalToast } from '@/hooks/useToast';
+import { switchToCatView } from '@/store/modalContentSlice';
+import { clearSharedUrl } from '@/store/sharedUrlSlice';
+
+const Breeds = () => {
+ const [selectedBreed, setSelectedBreed] = useState(null);
+ const [currentBreedCats, setCurrentBreedCats] = useState([]);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [shouldOpenModal, setShouldOpenModal] = useState(false);
+ const [searchParams, setSearchParams] = useSearchParams();
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ dispatch(
+ setHeaderData({
+ title: 'Breeds',
+ description: 'Discover the wonderful world of cat breeds'
+ })
+ );
+ }, [dispatch]);
+
+ const {
+ data: breedsData,
+ isLoading,
+ error,
+ } = useBreeds();
+
+ const { data: catsForBreed } = useCatsByBreed(
+ selectedBreed?.id || '',
+ 10,
+ { enabled: !!selectedBreed }
+ ) as { data: Breed[] | undefined };
+
+ useEffect(() => {
+ if (selectedBreed?.id && catsForBreed !== undefined && shouldOpenModal) {
+ if (catsForBreed.length > 0) {
+ const images = catsForBreed.map(cat => cat.url);
+ setCurrentBreedCats(images);
+ setIsModalOpen(true);
+ setShouldOpenModal(false);
+ } else {
+ setCurrentBreedCats([])
+ showGlobalToast({
+ message: `Unfortunately all the cats ran away!!`,
+ color: 'error'
+ });
+ const newParams = new URLSearchParams(searchParams);
+ newParams.delete('breed_id');
+ setSearchParams(newParams);
+ setShouldOpenModal(false);
+ }
+ }
+ }, [selectedBreed?.id, catsForBreed, shouldOpenModal, searchParams, setSearchParams]);
+
+ const breeds = (breedsData ?? []) as Breed[];
+
+ const handleBreedClick = (breed: Breed) => {
+ setSelectedBreed(breed);
+ setShouldOpenModal(true);
+ const newParams = new URLSearchParams(searchParams);
+ newParams.set('breed_id', breed.id);
+ setSearchParams(newParams);
+ };
+
+ const handleModalClose = () => {
+ const newParams = new URLSearchParams(searchParams);
+ newParams.delete('breed_id');
+ setSearchParams(newParams);
+ setIsModalOpen(false);
+ dispatch(switchToCatView(null));
+ dispatch(clearSharedUrl())
+ }
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return (
+
+
Error loading breeds: {error.message}
+
+ );
+ }
+
+ return (
+
+
+ {breeds.map((breed: Breed) => (
+ handleBreedClick(breed)}
+ />
+ ))}
+
+
+ {selectedBreed && (
+
+
+
+ )}
+
+ )
+}
+
+export default Breeds
\ No newline at end of file
diff --git a/src/pages/Catlist.tsx b/src/pages/Catlist.tsx
new file mode 100644
index 00000000..2d661006
--- /dev/null
+++ b/src/pages/Catlist.tsx
@@ -0,0 +1,144 @@
+import { useState, useEffect } from 'react';
+import { setHeaderData } from '@store/headerSlice';
+import { useAppDispatch } from '@store/hooks';
+import GridLayout from '@components/ui/GridLayout/GridLayout';
+import CatCard from '@components/ui/CatCard/CatCard';
+import Filters from '@components/ui/Filters/Filters';
+import { useLocalStorage } from '@hooks';
+import { useDynamicInfiniteCats, useCatById } from '@/api/hooks';
+import type { Cat } from '@types';
+import { GridSkeleton } from '@/components/ui/Skeletons/Skeletons';
+import { useSearchParams } from 'react-router-dom';
+import { Modal } from '@/components/ui/Modal/Modal';
+import ModalContent from '@/components/ui/Modal/ModalContent';
+import { clearSharedUrl } from '@/store/sharedUrlSlice';
+
+
+const Catlist = () => {
+ const limit = 10;
+ const [searchParams, setSearchParams] = useSearchParams();
+ const itemId = searchParams.get('cat_id');
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedCat, setSelectedCat] = useState(null);
+
+ const [hasBreeds, setHasBreeds] = useLocalStorage('cat-breed-filter', 1);
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ dispatch(
+ setHeaderData({
+ title: 'Cats',
+ description: 'Discover amazing cats and their breeds!'
+ })
+ );
+ }, [dispatch]);
+
+ const {
+ data,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isLoading,
+ error,
+ } = useDynamicInfiniteCats({ has_breeds: hasBreeds }, limit);
+
+ const { data: sharedCat } = useCatById(
+ itemId || '',
+ { enabled: !!itemId && !selectedCat && !isModalOpen }
+ ) as { data: Cat | undefined };
+
+ useEffect(() => {
+ if (sharedCat && itemId && !selectedCat) {
+ setSelectedCat(sharedCat);
+ setIsModalOpen(true);
+ }
+ }, [sharedCat, itemId, selectedCat, isModalOpen]);
+
+ const allCatImages = (data?.pages.flat() || []) as Cat[];
+
+ const handleCatClick = (cat: Cat) => {
+ setSelectedCat(cat)
+ const newParams = new URLSearchParams(searchParams);
+ newParams.set('cat_id', cat.id);
+ setSearchParams(newParams);
+ setIsModalOpen(true);
+ }
+
+ const handleModalClose = () => {
+ const newParams = new URLSearchParams(searchParams);
+ newParams.delete('cat_id');
+ setSearchParams(newParams);
+ setIsModalOpen(false);
+ dispatch(clearSharedUrl())
+ }
+
+ const handleLoadMore = () => {
+ if (hasNextPage && !isFetchingNextPage) {
+ fetchNextPage();
+ }
+ };
+
+ if (isLoading) {
+ return (
+ <>
+
+
+ >
+ );
+ }
+
+ if (error) {
+ return (
+
+
Error loading cats. Please try again later.
+
+ );
+ }
+
+ return (
+
+
+
+ {allCatImages.map(cat => (
+ handleCatClick(cat)}
+ />
+ ))}
+
+
+ {isFetchingNextPage &&
+
+ }
+
+ {hasNextPage && (
+
+
+ {isFetchingNextPage ? 'Loading...' : 'Load More Cats'}
+
+
+ )}
+
+ {selectedCat && (
+
selectedCat.height ? 'Landscape' : 'Portrait'}
+ >
+
+
+ )}
+
+ )
+}
+
+export default Catlist
\ No newline at end of file
diff --git a/src/pages/Favorites.tsx b/src/pages/Favorites.tsx
new file mode 100644
index 00000000..69fe4173
--- /dev/null
+++ b/src/pages/Favorites.tsx
@@ -0,0 +1,126 @@
+import { useEffect, useState } from 'react';
+import { setHeaderData } from '@store/headerSlice';
+import { useAppDispatch } from '@store/hooks';
+import { setSharedUrl, clearSharedUrl } from '@/store/sharedUrlSlice';
+import { useFavorites } from '@api/hooks';
+import { useSearchParams, NavLink } from 'react-router-dom'
+import GridLayout from '@components/ui/GridLayout/GridLayout';
+import CatCard from '@components/ui/CatCard/CatCard';
+import type { FavoriteItem, Cat } from '@types';
+import { GridSkeleton } from '@/components/ui/Skeletons/Skeletons';
+import { Modal } from '@/components/ui/Modal/Modal';
+import ModalContent from '@/components/ui/Modal/ModalContent';
+
+const Favorites = () => {
+ const dispatch = useAppDispatch();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedCat, setSelectedCat] = useState(null);
+
+ const {
+ data: favorites,
+ isLoading,
+ } = useFavorites({
+ sub_id: 'user-gwi'
+ });
+
+ useEffect(() => {
+ const headerDescription = (!favorites || !Array.isArray(favorites) || favorites.length === 0)
+ ? 'No favorite cats yet. Start exploring and add some favorites!'
+ : 'Keep track of your favorite cats here.';
+
+ dispatch(
+ setHeaderData({
+ title: 'Favorites',
+ description: headerDescription
+ })
+ );
+ }, [dispatch, favorites]);
+
+ const handleCatClick = (cat: Cat) => {
+ setSelectedCat(cat)
+ const newParams = new URLSearchParams(searchParams);
+ newParams.set('cat_id', cat.id);
+ setSearchParams(newParams);
+ setIsModalOpen(true);
+ dispatch(setSharedUrl(`?cat_id=${cat.id}`));
+ }
+
+ const handleModalClose = () => {
+ const newParams = new URLSearchParams(searchParams);
+ newParams.delete('cat_id');
+ setSearchParams(newParams);
+ setIsModalOpen(false);
+ dispatch(clearSharedUrl())
+ }
+
+
+ if (isLoading) {
+ return (
+ <>
+
+ >
+ );
+ }
+
+
+ if (!favorites || !Array.isArray(favorites) || favorites.length === 0) {
+ return (
+
+
Do you want to explore and add some favorites?
+
+
+ Explore Cats
+
+
+ Explore Breeds
+
+
+
+ );
+ }
+
+ return (
+
+
+ {favorites.map((favorite: FavoriteItem) => {
+ const catData: Cat = {
+ id: favorite.image_id,
+ url: favorite.image?.url || '',
+ width: favorite.image?.width || 0,
+ height: favorite.image?.height || 0,
+ breeds: []
+ };
+ return (
+ handleCatClick(catData)}
+ />
+ );
+ })}
+
+ {selectedCat && (
+ selectedCat.height ? 'Landscape' : 'Portrait'}
+ >
+
+
+ )}
+
+ )
+}
+
+export default Favorites
\ No newline at end of file
diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx
new file mode 100644
index 00000000..6c13c650
--- /dev/null
+++ b/src/pages/NotFound.tsx
@@ -0,0 +1,28 @@
+import { useEffect } from 'react';
+import { useAppDispatch } from '@store/hooks';
+import { setHeaderData } from '@store/headerSlice';
+import catNotFound from '@assets/404.png';
+
+const NotFound = () => {
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ dispatch(
+ setHeaderData({
+ title: 'Meow! Page Not Found',
+ description: 'Oops! This page seems to have wandered off like a curious cat.'
+ })
+ );
+ }, [dispatch]);
+
+ return (
+
+ );
+}
+
+export default NotFound
\ No newline at end of file
diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx
new file mode 100644
index 00000000..421a8b32
--- /dev/null
+++ b/src/routes/routes.tsx
@@ -0,0 +1,32 @@
+import { createBrowserRouter } from 'react-router-dom';
+
+import AppLayout from '@components/layout/AppLayout';
+import CatList from '@/pages/Catlist';
+import Breeds from '@pages/Breeds';
+import Favorites from '@pages/Favorites';
+import NotFound from '@pages/NotFound';
+
+export const router = createBrowserRouter([
+ {
+ path: '/',
+ element: ,
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ {
+ path: 'breeds',
+ element: ,
+ },
+ {
+ path: 'favorites',
+ element: ,
+ },
+ {
+ path: '*',
+ element: , // Could use the errorElement property but needed to reapply layout components
+ },
+ ],
+ },
+]);
diff --git a/src/store/favoritesCounterSlice.ts b/src/store/favoritesCounterSlice.ts
new file mode 100644
index 00000000..c8cffe8f
--- /dev/null
+++ b/src/store/favoritesCounterSlice.ts
@@ -0,0 +1,18 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+const initialState = {
+ count: 0,
+};
+
+const favoritesCounterSlice = createSlice({
+ name: 'favoritesCounter',
+ initialState,
+ reducers: {
+ totalCount: (state, action) => {
+ state.count = action.payload;
+ },
+ },
+});
+
+export const { totalCount } = favoritesCounterSlice.actions;
+export default favoritesCounterSlice.reducer;
\ No newline at end of file
diff --git a/src/store/headerSlice.ts b/src/store/headerSlice.ts
new file mode 100644
index 00000000..8706309a
--- /dev/null
+++ b/src/store/headerSlice.ts
@@ -0,0 +1,23 @@
+import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
+import type { HeaderState } from '@types';
+
+const initialState: HeaderState = {
+ title: 'Meowbase',
+ description: undefined
+};
+
+const headerSlice = createSlice({
+ name: 'header',
+ initialState,
+ reducers: {
+ setHeaderData: (_state, action: PayloadAction) => {
+ return { ...action.payload };
+ },
+ resetHeader: () => {
+ return initialState;
+ },
+ },
+});
+
+export const { setHeaderData, resetHeader } = headerSlice.actions;
+export default headerSlice.reducer;
diff --git a/src/store/hooks.ts b/src/store/hooks.ts
new file mode 100644
index 00000000..4f2a9190
--- /dev/null
+++ b/src/store/hooks.ts
@@ -0,0 +1,6 @@
+import { useDispatch, useSelector } from 'react-redux';
+import type { RootState, AppDispatch } from './store';
+
+export const useAppDispatch = useDispatch.withTypes();
+export const useAppSelector = useSelector.withTypes();
+export const useLoading = () => useAppSelector((state) => state.loading.isLoading);
\ No newline at end of file
diff --git a/src/store/loadingSlice.ts b/src/store/loadingSlice.ts
new file mode 100644
index 00000000..67d9489b
--- /dev/null
+++ b/src/store/loadingSlice.ts
@@ -0,0 +1,19 @@
+import { createSlice } from '@reduxjs/toolkit';
+import type { LoadingState } from '@types';
+
+const initialState: LoadingState = {
+ isLoading: false,
+};
+
+const loadingSlice = createSlice({
+ name: 'loading',
+ initialState,
+ reducers: {
+ setLoading: (state, action) => {
+ state.isLoading = action.payload;
+ },
+ },
+});
+
+export const { setLoading } = loadingSlice.actions;
+export default loadingSlice.reducer;
\ No newline at end of file
diff --git a/src/store/modalContentSlice.ts b/src/store/modalContentSlice.ts
new file mode 100644
index 00000000..551b7884
--- /dev/null
+++ b/src/store/modalContentSlice.ts
@@ -0,0 +1,42 @@
+import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
+import type { ModalContentState, BreedContent, CatContent } from '@types';
+
+const initialState: ModalContentState = {
+ breedContent: null,
+ catContent: null,
+ currentView: null,
+};
+
+const modalContentSlice = createSlice({
+ name: 'modalContent',
+ initialState,
+ reducers: {
+ setBreedContent: (state, action: PayloadAction) => {
+ state.breedContent = action.payload;
+ state.currentView = 'breed';
+ },
+ setCatContent: (state, action: PayloadAction) => {
+ state.catContent = action.payload;
+ state.currentView = 'cat';
+ },
+ switchToBreedView: (state) => {
+ state.currentView = 'breed';
+ },
+ switchToCatView: (state, action: PayloadAction<'cat' | null>) => {
+ state.currentView = action.payload;
+ },
+ clearModalContent: () => {
+ return initialState;
+ },
+ },
+});
+
+export const {
+ setBreedContent,
+ setCatContent,
+ switchToBreedView,
+ switchToCatView,
+ clearModalContent
+} = modalContentSlice.actions;
+
+export default modalContentSlice.reducer;
\ No newline at end of file
diff --git a/src/store/sharedUrlSlice.ts b/src/store/sharedUrlSlice.ts
new file mode 100644
index 00000000..f0487fd7
--- /dev/null
+++ b/src/store/sharedUrlSlice.ts
@@ -0,0 +1,25 @@
+import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
+
+interface SharedUrlState {
+ currentUrl: string;
+}
+
+const initialState: SharedUrlState = {
+ currentUrl: '',
+};
+
+const sharedUrlSlice = createSlice({
+ name: 'sharedUrl',
+ initialState,
+ reducers: {
+ setSharedUrl: (state, action: PayloadAction) => {
+ state.currentUrl = action.payload;
+ },
+ clearSharedUrl: (state) => {
+ state.currentUrl = '';
+ },
+ },
+});
+
+export const { setSharedUrl, clearSharedUrl } = sharedUrlSlice.actions;
+export default sharedUrlSlice.reducer;
diff --git a/src/store/store.ts b/src/store/store.ts
new file mode 100644
index 00000000..c503bc9e
--- /dev/null
+++ b/src/store/store.ts
@@ -0,0 +1,20 @@
+import { configureStore } from '@reduxjs/toolkit';
+import headerReducer from './headerSlice';
+import loadingReducer from './loadingSlice';
+import modalContentReducer from './modalContentSlice';
+import sharedUrlReducer from './sharedUrlSlice';
+import favoritesCounterReducer from './favoritesCounterSlice'
+
+
+export const store = configureStore({
+ reducer: {
+ header: headerReducer,
+ loading: loadingReducer,
+ sharedUrl: sharedUrlReducer,
+ modalContent: modalContentReducer,
+ favoritesCounter: favoritesCounterReducer
+ },
+});
+
+export type RootState = ReturnType;
+export type AppDispatch = typeof store.dispatch;
\ No newline at end of file
diff --git a/src/styles/_buttons.scss b/src/styles/_buttons.scss
new file mode 100644
index 00000000..5318f698
--- /dev/null
+++ b/src/styles/_buttons.scss
@@ -0,0 +1,72 @@
+@use './variables' as *;
+@use './mixins' as *;
+
+.btn {
+ padding: $spacing-sm $spacing-lg;
+ font-size: $font-size-md;
+ border: none;
+ border-radius: $radius-sm;
+ cursor: pointer;
+ font-family: inherit;
+ font-weight: 500;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ transition: all 0.2s ease-in-out;
+
+}
+
+.btn-primary {
+ background-color: $color-button;
+ color: $color-white;
+
+ &:hover:not(:disabled) {
+ background-color: $color-button-hover;
+ transform: translateY(-2px);
+ }
+
+ &:active:not(:disabled) {
+ background-color: $color-button-disabled;
+ transform: translateY(0);
+ }
+}
+
+.btn-disabled,
+.btn:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+
+ &:hover {
+ transform: none;
+ }
+}
+
+.catlist__load-more {
+ text-align:center;
+ margin-block: $spacing-xl;
+}
+
+// Loading state
+.btn-loading {
+ position: relative;
+ color: transparent;
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 20px;
+ height: 20px;
+ border: 2px solid transparent;
+ border-top: 2px solid currentColor;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ }
+}
+
+@keyframes spin {
+ 0% { transform: translate(-50%, -50%) rotate(0deg); }
+ 100% { transform: translate(-50%, -50%) rotate(360deg); }
+}
diff --git a/src/styles/_globals.scss b/src/styles/_globals.scss
new file mode 100644
index 00000000..90cccf5e
--- /dev/null
+++ b/src/styles/_globals.scss
@@ -0,0 +1,76 @@
+@use './variables' as *;
+@use './mixins' as *;
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: $font-base;
+ color: $color-text;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ min-height: 100vh;
+}
+
+a {
+ color: $color-primary;
+ text-decoration: none;
+}
+
+#root {
+ width: 100%;
+ min-height: 100vh;
+ margin: 0;
+ padding: 0;
+}
+
+h3 {
+ font-size: $font-size-xl;
+ font-weight: 700;
+ margin-bottom: $spacing-md;
+ color: $color-text;
+ letter-spacing: -0.5px;
+ text-align: center;
+}
+
+.app-layout {
+ min-height: 100vh;
+ width: 100%;
+}
+
+.pnf-image {
+ display:block;
+ margin:auto;
+ text-align:center;
+ max-width:300px;
+}
+
+.error-message {
+ font-size: $font-size-lg;
+ color: $color-white;
+ text-align:center;
+}
+
+.main-content {
+ padding-block: $spacing-xl;
+ padding-inline: $spacing-lg;
+ min-height: 100vh;
+ width: 100%;
+ max-width: 1600px;
+ margin: auto;
+
+ @media (min-width: 768px) {
+ padding-top: $spacing-xxxl;
+ padding-inline: $spacing-lg;
+ }
+}
+
+@media (max-width:767px) {
+ .mobile-hidden {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss
new file mode 100644
index 00000000..1ff32fef
--- /dev/null
+++ b/src/styles/_mixins.scss
@@ -0,0 +1,5 @@
+@mixin flex-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
\ No newline at end of file
diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss
new file mode 100644
index 00000000..d19a6012
--- /dev/null
+++ b/src/styles/_variables.scss
@@ -0,0 +1,62 @@
+// Typography
+$font-base: 'Inter',
+ system-ui,
+ -apple-system,
+ 'Segoe UI',
+ Roboto,
+ 'Helvetica Neue',
+ Arial;
+
+$font-size-sm: 14px;
+$font-size-md: 16px;
+$font-size-lg: 20px;
+$font-size-xl: 30px;
+$font-size-xxl: 40px;
+
+// Color Palette
+$color-primary: #ff9800;
+$color-secondary: #3ed6ff;
+
+$color-button: #007bff;
+$color-button-hover: #0056b3;
+$color-button-disabled: #004085;
+
+$color-white: #fff;
+$color-dark: #000;
+$color-grey: #555;
+$color-red: #ff3f3f;
+
+$color-text: #2c2c2c;
+
+$color-success-dark: #155724;
+$color-success-light: #28a745;
+$color-error-dark: #721c24;
+$color-error-light: #f8d7da;
+$color-info-dark: #0c5460;
+$color-info-light: #d1ecf1;
+
+$color-background-dark: #2c2c2c;
+$color-background-light: #f8f9fa;
+
+$color-white-60: rgba(255, 255, 255, 0.6);
+$color-white-70: rgba(255, 255, 255, 0.7);
+$color-white-80: rgba(255, 255, 255, 0.8);
+
+$color-border-light: #e5e5e5;
+$color-border-dark: #2c2c2c;
+
+// Spacing
+
+$spacing-xs: 4px;
+$spacing-sm: 8px;
+$spacing-md: 16px;
+$spacing-lg: 24px;
+$spacing-xl: 32px;
+$spacing-xxl: 48px;
+$spacing-xxxl: 60px;
+
+$radius-xs: 4px;
+$radius-sm: 8px;
+$radius-md: 16px;
+$radius-lg: 24px;
+$radius-xl: 30px;
\ No newline at end of file
diff --git a/src/styles/index.scss b/src/styles/index.scss
new file mode 100644
index 00000000..faa9a457
--- /dev/null
+++ b/src/styles/index.scss
@@ -0,0 +1,5 @@
+@use './variables' as *;
+@use './mixins' as *;
+@use './globals';
+@use './layout/grid';
+@use './buttons';
\ No newline at end of file
diff --git a/src/styles/layout/_grid.scss b/src/styles/layout/_grid.scss
new file mode 100644
index 00000000..937d1530
--- /dev/null
+++ b/src/styles/layout/_grid.scss
@@ -0,0 +1,18 @@
+@use '../../styles/variables' as *;
+
+.grid {
+ margin-bottom: $spacing-md;
+
+ .card {
+ transition: transform 0.3s ease;
+ }
+
+ &:hover .card:hover {
+ transform: scale(1.02);
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+ z-index: 10;
+ }
+ &:hover > .card:not(:hover) {
+ opacity: 0.8;
+ }
+}
diff --git a/src/styles/styles.d.ts b/src/styles/styles.d.ts
new file mode 100644
index 00000000..8b49d09e
--- /dev/null
+++ b/src/styles/styles.d.ts
@@ -0,0 +1,5 @@
+declare module '*.module.scss' {
+ const classes: { readonly [key: string]: string };
+ export default classes;
+}
+declare module '*.scss';
\ No newline at end of file
diff --git a/src/testSetup.ts b/src/testSetup.ts
new file mode 100644
index 00000000..010b0b5d
--- /dev/null
+++ b/src/testSetup.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom'
\ No newline at end of file
diff --git a/src/types/api.ts b/src/types/api.ts
new file mode 100644
index 00000000..c59c9eff
--- /dev/null
+++ b/src/types/api.ts
@@ -0,0 +1,80 @@
+// Core API response types for The Cat API
+
+// Base Image type
+export interface BaseImage {
+ id: string;
+ url: string;
+ width?: number;
+ height?: number;
+}
+
+// Weight type
+export interface Weight {
+ imperial: string;
+ metric: string;
+}
+
+// Cat
+export interface Cat {
+ id: string;
+ url: string;
+ width: number;
+ height: number;
+ breeds?: [];
+}
+
+// Breed
+export interface Breed {
+ url: string;
+ id: string;
+ name: string;
+ description?: string;
+ temperament?: string;
+ origin?: string;
+ life_span?: string;
+ weight?: Weight;
+ image?: BaseImage;
+}
+
+// Favorite Item
+export interface FavoriteItem {
+ id: number;
+ image_id: string;
+ sub_id?: string;
+ created_at: string;
+ image?: BaseImage;
+}
+
+// Breed Data
+export interface BreedData {
+ weight: Weight;
+ id: string;
+ name: string;
+ temperament: string;
+ origin: string;
+ country_code: string;
+ description: string;
+ life_span: string;
+ indoor: number;
+ lap: number;
+ adaptability: number;
+ affection_level: number;
+ child_friendly: number;
+ dog_friendly: number;
+ energy_level: number;
+ grooming: number;
+ health_issues: number;
+ intelligence: number;
+ shedding_level: number;
+ social_needs: number;
+ stranger_friendly: number;
+ vocalisation: number;
+ experimental: number;
+ hairless: number;
+ natural: number;
+ rare: number;
+ rex: number;
+ suppressed_tail: number;
+ short_legs: number;
+ hypoallergenic: number;
+}
\ No newline at end of file
diff --git a/src/types/components.ts b/src/types/components.ts
new file mode 100644
index 00000000..f09e2fb8
--- /dev/null
+++ b/src/types/components.ts
@@ -0,0 +1,126 @@
+import type { ReactNode, ElementType } from 'react';
+import type { BaseImage, BreedData, Weight } from './api';
+
+// Header types
+export interface NavigationItem {
+ href: string;
+ title: string;
+}
+
+// Card component types
+export interface CatCardProps {
+ id: string;
+ imageUrl: string;
+ alt?: string;
+ children?: ReactNode;
+ className?: string;
+ openModal?: () => void;
+}
+
+// Favorite button types
+export interface FavoriteBtnProps {
+ imageId: string;
+ className?: string;
+ title?: string;
+ disabled?: boolean;
+}
+
+// Grid layout types
+export interface GridProps {
+ children: ReactNode;
+ columns?: number;
+ gap?: string;
+ className?: string;
+ as?: ElementType;
+};
+
+// Breed card types
+export interface BreedCardProps {
+ id: string;
+ name: string;
+ description?: string;
+ temperament?: string;
+ origin?: string;
+ lifeSpan?: string;
+ image?: BaseImage;
+ onClick?: () => void;
+}
+
+// Filters types
+export interface FiltersProps {
+ hasBreeds: number;
+ onChange: (value: number) => void;
+}
+
+// Grid skeleton types
+export interface GridSkeletonProps {
+ type?: 'cat' | 'breed';
+ count?: number;
+ columns?: number;
+ gap?: string;
+ className?: string;
+}
+
+// Favorite button types
+export interface FavoriteBtnProps {
+ imageId: string;
+ className?: string;
+ title?: string;
+ disabled?: boolean;
+}
+
+// Modal component types
+export interface ModalProps {
+ isOpen: boolean;
+ setIsOpen: (isOpen: boolean) => void;
+ onClose: () => void;
+ children: ReactNode;
+ title?: string;
+ orientation?: string;
+ catId?: string | number;
+}
+
+// Modal content interface that can handle Cat, Breed, or custom modal data
+export interface ModalDetails {
+ id: string;
+ url?: string;
+ width?: number;
+ height?: number;
+ breeds?: BreedData[];
+ name?: string;
+ description?: string;
+ temperament?: string;
+ origin?: string;
+ life_span?: string;
+ weight?: Weight;
+ image?: BaseImage;
+ country_code?: string;
+ adaptability?: number;
+ affection_level?: number;
+ child_friendly?: number;
+ dog_friendly?: number;
+ energy_level?: number;
+ grooming?: number;
+ health_issues?: number;
+ intelligence?: number;
+ shedding_level?: number;
+ social_needs?: number;
+ stranger_friendly?: number;
+ vocalisation?: number;
+ indoor?: number;
+ lap?: number;
+ hypoallergenic?: number;
+ hairless?: number;
+ rex?: number;
+ natural?: number;
+ rare?: number;
+ experimental?: number;
+ suppressed_tail?: number;
+ short_legs?: number;
+}
+
+export interface ModalContentProps {
+ content: ModalDetails;
+ currentBreedCats?: string[];
+ hasBreedDetails?: boolean;
+}
\ No newline at end of file
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 00000000..7f04ab42
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,3 @@
+export * from './components';
+export * from './store';
+export * from './api';
\ No newline at end of file
diff --git a/src/types/store.ts b/src/types/store.ts
new file mode 100644
index 00000000..69d737af
--- /dev/null
+++ b/src/types/store.ts
@@ -0,0 +1,29 @@
+import type { BreedData } from './api';
+import type { ModalDetails } from './components';
+
+// Header state types
+export interface HeaderState {
+ title: string;
+ description?: string;
+}
+
+// Loading state types
+export interface LoadingState {
+ isLoading: boolean;
+}
+
+// Modal content state types
+export interface BreedContent {
+ currentBreedCats: string[];
+ breedData?: BreedData;
+}
+
+export interface CatContent {
+ catDetails: ModalDetails;
+}
+
+export interface ModalContentState {
+ breedContent: BreedContent | null;
+ catContent: CatContent | null;
+ currentView: 'breed' | 'cat' | null;
+}
\ No newline at end of file
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 00000000..151aa685
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
\ No newline at end of file
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 00000000..efc9b7cf
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,44 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Path mapping */
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"],
+ "@assets/*": ["./src/assets/*"],
+ "@components/*": ["./src/components/*"],
+ "@hooks": ["./src/hooks/index"],
+ "@hooks/*": ["./src/hooks/*"],
+ "@pages/*": ["./src/pages/*"],
+ "@store/*": ["./src/store/*"],
+ "@types": ["./src/types/index"],
+ "@types/*": ["./src/types/*"],
+ "@styles/*": ["./src/styles/*"],
+ "@api/*": ["./src/api/*"]
+ },
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
+
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..1ffef600
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 00000000..f85a3990
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 00000000..c3c44cc6
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vitest/config';
+import react from '@vitejs/plugin-react-swc';
+import tsconfigPaths from 'vite-tsconfig-paths';
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react(), tsconfigPaths()],
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: './src/testSetup.ts'
+ }
+});