diff --git "a/.github/ISSUE_TEMPLATE/\352\270\260\353\263\270-\355\203\254\355\224\214\353\246\277.md" "b/.github/ISSUE_TEMPLATE/\352\270\260\353\263\270-\355\203\254\355\224\214\353\246\277.md" index a10f64c..b6db260 100644 --- "a/.github/ISSUE_TEMPLATE/\352\270\260\353\263\270-\355\203\254\355\224\214\353\246\277.md" +++ "b/.github/ISSUE_TEMPLATE/\352\270\260\353\263\270-\355\203\254\355\224\214\353\246\277.md" @@ -1,16 +1,14 @@ --- name: 기본 탬플릿 about: 이 탬플릿을 이용해 주세요 -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- -## (제목) +## 작업 내용 -### 작업 내용 - 작업내용 1 - 작업내용 2 -### 기타 +## 기타 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ea6e835..89d2020 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ # 개요 -- Fixes # (이슈 번호) +- Closes # (이슈 번호) - 페이지 || 컴포넌트 변경 시 이미지 첨부 (기존과 다를 경우) @@ -12,4 +12,4 @@ - [ ] Docs updated required - [ ] Chore -# 상세 내용 +## 상세 내용 diff --git a/.github/workflows/build-check.yaml b/.github/workflows/build-check.yaml new file mode 100644 index 0000000..75a777b --- /dev/null +++ b/.github/workflows/build-check.yaml @@ -0,0 +1,32 @@ +name: lint & build check + +on: + pull_request: + branches: + - develop + - main + +jobs: + build: + name: build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22.18.0" + cache: npm + + - name: Install dependencies + run: npm ci + + - name: ESLint Check + run: npm run lint + + - name: Stylelint Check + run: npm run stylelint + + - name: Build React + run: npm run build --profile diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..52cff2b --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,32 @@ +name: Deploy Release to Vercel + +on: + release: + types: [published] + +jobs: + deploy: + name: deploy + runs-on: ubuntu-latest + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + steps: + - uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22.18.0" + cache: "npm" + - name: Install Vercel CLI + run: npm i -g vercel@latest + - name: Pull Vercel env (production) + run: vercel pull --yes --environment=production --token "${VERCEL_TOKEN}" + - name: Vercel build (production) + run: vercel build --prod --token "${VERCEL_TOKEN}" + - name: Deploy to Vercel (Production, prebuilt) + run: vercel deploy --prebuilt --prod --token "${VERCEL_TOKEN}" diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml new file mode 100644 index 0000000..3ad9eff --- /dev/null +++ b/.github/workflows/preview.yaml @@ -0,0 +1,33 @@ +name: Deploy Preview to Vercel + +on: + push: + branches: + - develop + +jobs: + deploy: + name: deploy Preview + runs-on: ubuntu-latest + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + steps: + - uses: actions/checkout@v4 + with: + ref: develop + fetch-depth: 0 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22.18.0" + cache: "npm" + - name: Install Vercel CLI + run: npm i -g vercel@latest + - name: Pull Vercel env (production) + run: vercel pull --yes --environment=production --token "${VERCEL_TOKEN}" + - name: Vercel build (production) + run: vercel build --token "${VERCEL_TOKEN}" + - name: Deploy to Vercel (Production, prebuilt) + run: vercel deploy --prebuilt --token "${VERCEL_TOKEN}" diff --git a/.github/workflows/version.yaml b/.github/workflows/version.yaml new file mode 100644 index 0000000..6863d72 --- /dev/null +++ b/.github/workflows/version.yaml @@ -0,0 +1,30 @@ +name: Create release & tag +on: + push: + branches: + - main + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22.18.0 + cache: "npm" + - name: Bump version and push tag + id: tag_version + uses: mathieudutour/github-tag-action@v6.2 + with: + github_token: ${{ secrets.TOKEN }} + - name: Create a GitHub release + uses: ncipollo/release-action@v1 + with: + tag: ${{ steps.tag_version.outputs.new_tag }} + name: Release ${{ steps.tag_version.outputs.new_tag }} + body: ${{ steps.tag_version.outputs.changelog }} diff --git a/.gitignore b/.gitignore index a547bf3..d879e24 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,17 @@ dist-ssr *.njsproj *.sln *.sw? + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +venv/ +ENV/ +env/ +.venv + +# AI API (별도 Git 저장소로 관리) +recommendation-api/ \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..12ed950 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npm run lint-front diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..fcb335d --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["stylelint-config-standard", "stylelint-config-recommended"], + "plugins": ["stylelint-order"], + "customSyntax": "postcss-styled-syntax", + "rules": { + "media-query-no-invalid": null, + "declaration-empty-line-before": [ + "always", + { + "ignore": ["first-nested", "after-comment", "after-declaration", "inside-single-line-block"] + } + ], + "order/order": ["custom-properties", "declarations"], + + "order/properties-order": null, + "nesting-selector-no-missing-scoping-root": null + } +} diff --git a/check-node-version.cjs b/check-node-version.cjs new file mode 100644 index 0000000..f398194 --- /dev/null +++ b/check-node-version.cjs @@ -0,0 +1,7 @@ +const required = "22.18.0"; +const current = process.versions.node; + +if (current !== required) { + console.error(`❌ Node ${required} 버전이 필요합니다. 현재 버전: ${current}`); + process.exit(1); +} diff --git a/eslint.config.js b/eslint.config.js index cee1e2c..a4b8a96 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,29 +1,57 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import { defineConfig, globalIgnores } from 'eslint/config' +import js from "@eslint/js"; +import importPlugin from "eslint-plugin-import"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import { defineConfig, globalIgnores } from "eslint/config"; + +import globals from "globals"; export default defineConfig([ - globalIgnores(['dist']), + globalIgnores(["dist"]), { - files: ['**/*.{js,jsx}'], + plugins: { + import: importPlugin, + }, + files: ["**/*.{js,jsx}"], extends: [ js.configs.recommended, - reactHooks.configs['recommended-latest'], + reactHooks.configs["recommended-latest"], reactRefresh.configs.vite, ], languageOptions: { ecmaVersion: 2020, globals: globals.browser, parserOptions: { - ecmaVersion: 'latest', + ecmaVersion: "latest", ecmaFeatures: { jsx: true }, - sourceType: 'module', + sourceType: "module", }, }, rules: { - 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + // 파일 뒤에 js, jsx 사용 금지 + "import/extensions": [ + "error", + "ignorePackages", + { + js: "never", + jsx: "never", + ts: "never", + tsx: "never", + }, + ], + "react-refresh/only-export-components": "off", + "no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }], + }, + settings: { + "import/resolver": { + alias: { + map: [["@", "./src"]], + extensions: [".js", ".jsx"], + }, + node: { + extensions: [".js", ".jsx"], + }, + }, }, }, -]) +]); diff --git a/index.html b/index.html index 0ab3cef..6578b2b 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,8 @@
+ +
diff --git a/package-lock.json b/package-lock.json index 4f89b4d..2097728 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "axios": "^1.13.1", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-error-boundary": "^6.0.0", "react-router-dom": "^7.9.5", "recharts": "^3.3.0", "styled-components": "^6.1.19" @@ -21,11 +22,23 @@ "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", "eslint": "^9.36.0", + "eslint-import-resolver-alias": "^1.1.2", + "eslint-import-resolver-node": "^0.3.9", + "eslint-plugin-import": "^2.32.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", "globals": "^16.4.0", + "husky": "^9.1.7", + "lint-staged": "^16.2.6", + "postcss-styled-syntax": "^0.7.1", "prettier": "^3.6.2", + "stylelint": "^16.25.0", + "stylelint-config-standard": "^39.0.1", + "stylelint-order": "^7.0.0", "vite": "^7.1.7" + }, + "engines": { + "node": "22.18.0" } }, "node_modules/@babel/code-frame": { @@ -59,6 +72,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -262,6 +276,15 @@ "@babel/core": "^7.0.0-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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -310,6 +333,180 @@ "node": ">=6.9.0" } }, + "node_modules/@cacheable/memoize": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@cacheable/memoize/-/memoize-2.0.3.tgz", + "integrity": "sha512-hl9wfQgpiydhQEIv7fkjEzTGE+tcosCXLKFDO707wYJ/78FVOlowb36djex5GdbSyeHnG62pomYLMuV/OT8Pbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.0.3" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.4.tgz", + "integrity": "sha512-cCmJKCKlT1t7hNBI1+gFCwmKFd9I4pS3zqBeNGXTSODnpa0EeDmORHY8oEMTuozfdg3cgsVh8ojLaPYb6eC7Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.2.0", + "@keyv/bigmap": "^1.1.0", + "hookified": "^1.12.2", + "keyv": "^5.5.3" + } + }, + "node_modules/@cacheable/memory/node_modules/@keyv/bigmap": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.2.0.tgz", + "integrity": "sha512-4Lme8NejkyetZ9oJ6u8NSf0iJEFFt7I+tyDI48wZlaFmbhDEh4nZg7bEPFPwCWkpIuL50/ukWBC9AHQTmdJLUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.2.0", + "hookified": "^1.12.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.5.4" + } + }, + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.4.tgz", + "integrity": "sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.2.0.tgz", + "integrity": "sha512-7xaQayO3msdVcxXLYcLU5wDqJBNdQcPPPHr6mdTEIQI7N7TbtSVVTpWOTfjyhg0L6AQwQdq7miKdWtTDBoBldQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "keyv": "^5.5.3" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.4.tgz", + "integrity": "sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "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", + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.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", + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "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/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "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-selector-parser": "^7.0.0" + } + }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz", + "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/JounQin" + } + }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", @@ -1032,6 +1229,51 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "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/@reduxjs/toolkit": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.0.tgz", @@ -1373,6 +1615,13 @@ "win32" ] }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -1507,12 +1756,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1566,6 +1823,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1600,6 +1858,35 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1623,83 +1910,309 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz", - "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/baseline-browser-mapping": { - "version": "2.8.23", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz", - "integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==", + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": ">=8" } }, - "node_modules/browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" - }, - "bin": { - "browserslist": "cli.js" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz", + "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "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/baseline-browser-mapping": { + "version": "2.8.23", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz", + "integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "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/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cacheable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.1.1.tgz", + "integrity": "sha512-LmF4AXiSNdiRbI2UjH8pAp9NIXxeQsTotpEaegPiDcnN0YPygDJDV3l/Urc0mL72JWdATEorKqIHEx55nDlONg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memoize": "^2.0.3", + "@cacheable/memory": "^2.0.3", + "@cacheable/utils": "^2.1.0", + "hookified": "^1.12.2", + "keyv": "^5.5.3", + "qified": "^0.5.0" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.4.tgz", + "integrity": "sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/call-bind-apply-helpers": { @@ -1715,6 +2228,23 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1772,6 +2302,39 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -1801,6 +2364,20 @@ "dev": true, "license": "MIT" }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1813,6 +2390,16 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1836,6 +2423,33 @@ "node": ">=18" } }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1860,6 +2474,16 @@ "node": ">=4" } }, + "node_modules/css-functions-list": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", + "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12 || >=16" + } + }, "node_modules/css-to-react-native": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", @@ -1871,22 +2495,49 @@ "postcss-value-parser": "^4.0.2" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", + "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": { - "internmap": "1 - 2" + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">=12" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-color": { @@ -1998,6 +2649,60 @@ "node": ">=12" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2029,6 +2734,42 @@ "dev": true, "license": "MIT" }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2038,6 +2779,32 @@ "node": ">=0.4.0" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2059,6 +2826,115 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2104,6 +2980,37 @@ "node": ">= 0.4" } }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-toolkit": { "version": "1.41.0", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.41.0.tgz", @@ -2185,6 +3092,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2239,6 +3147,114 @@ } } }, + "node_modules/eslint-import-resolver-alias": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz", + "integrity": "sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + }, + "peerDependencies": { + "eslint-plugin-import": ">=1.4.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "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", @@ -2369,6 +3385,36 @@ "dev": true, "license": "MIT" }, + "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", @@ -2383,20 +3429,57 @@ "dev": true, "license": "MIT" }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "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/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { "optional": true } } @@ -2414,6 +3497,19 @@ "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", @@ -2472,6 +3568,22 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -2512,6 +3624,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2522,6 +3675,19 @@ "node": ">=6.9.0" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2559,6 +3725,24 @@ "node": ">= 0.4" } }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2572,6 +3756,47 @@ "node": ">=10.13.0" } }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "16.5.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", @@ -2585,6 +3810,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true, + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2597,6 +3867,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2607,6 +3890,35 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -2634,6 +3946,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.2.0.tgz", + "integrity": "sha512-43XJKpwle72Ik5Zpam7MuzRWyNdwwdf6XHlh8wCj2PggvWf+v/Dm5B0dxGZOmddidgeO6Ofu9As/o231Ti/9PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.13.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -2646,6 +3971,42 @@ "node": ">= 0.4" } }, + "node_modules/hookified": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.13.0.tgz", + "integrity": "sha512-6sPYUY8olshgM/1LDNW4QZQN0IqgKhtl/1C8koNZBJrKLBk3AZl6chQtNwpNztvfiApHMEwMHek5rv993PRbWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2693,6 +4054,28 @@ "node": ">=0.8.19" } }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -2702,763 +4085,2581 @@ "node": ">=12" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "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==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "has-bigints": "^1.0.2" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "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/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "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": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "get-east-asian-width": "^1.3.1" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "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" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "is-extglob": "^2.1.1" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=0.10.0" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "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", - "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": ">=0.12.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==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-locate": { + "node_modules/is-plain-object": { "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==", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "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==", + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { - "callsites": "^3.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/picocolors": { + "node_modules/is-string": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": 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" + "which-typed-array": "^1.1.16" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "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==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, "license": "MIT" }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "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", - "engines": { - "node": ">=6" + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/react-dom": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", - "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "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-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "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/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" + "bin": { + "json5": "lib/cli.js" }, - "peerDependencies": { - "react": "^19.2.0" + "engines": { + "node": ">=6" } }, - "node_modules/react-is": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", - "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", - "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==", + "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": { - "@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 - } + "json-buffer": "3.0.1" } }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/react-router": { - "version": "7.9.5", + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, + "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/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", + "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.1", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "nano-spawn": "^2.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.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/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/micromatch/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/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/nano-spawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "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", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": 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", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sorting": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-9.1.0.tgz", + "integrity": "sha512-Mn8KJ45HNNG6JBpBizXcyf6LqY/qyqetGcou/nprDnFwBFBLGj0j/sNKV2lj2KMOVOwdXu14aEzqJv8CIV6e8g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8.4.20" + } + }, + "node_modules/postcss-styled-syntax": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/postcss-styled-syntax/-/postcss-styled-syntax-0.7.1.tgz", + "integrity": "sha512-V5Iy8JztqXOKnTojdytF8IJ3zDXyVR927XftBPinJa3TnKdChGvGzUNEYlNuDtR+iqpuFkwJMgZdaJarYfGFCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "typescript": "^5.7.3" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "postcss": "^8.5.1" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "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/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "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/qified": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.5.2.tgz", + "integrity": "sha512-7gJ6mxcQb9vUBOtbKm5mDevbe2uRcOEVp1g4gb/Q+oLntB3HY8eBhOYRxFI2mlDFlY1e4DOSCptzxarXRvzxCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.13.0" + }, + "engines": { + "node": ">=20" + } + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-error-boundary": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.0.0.tgz", + "integrity": "sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-is": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", + "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", + "peer": true, + "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-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.9.5", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.5.tgz", "integrity": "sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==", "license": "MIT", "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" + "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.5", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.5.tgz", + "integrity": "sha512-mkEmq/K8tKN63Ae2M7Xgz3c9l9YNbY+NHH6NNeUmLA3kDkhKXRsNb/ZpxaEunvGo2/3YXdk5EJU3Hxp3ocaBPw==", + "license": "MIT", + "dependencies": { + "react-router": "7.9.5" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/recharts": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.3.0.tgz", + "integrity": "sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==", + "license": "MIT", + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "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", + "peer": true + }, + "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/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": 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.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "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/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "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/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/styled-components": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", + "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "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.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/stylelint": { + "version": "16.25.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.25.0.tgz", + "integrity": "sha512-Li0avYWV4nfv1zPbdnxLYBGq4z8DVZxbRgx4Kn6V+Uftz1rMoF1qiEI3oL4kgWqyYgCgs7gT5maHNZ82Gk03vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3", + "@csstools/selector-specificity": "^5.0.0", + "@dual-bundle/import-meta-resolve": "^4.2.1", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^10.1.4", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^7.0.5", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.6", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "supports-hyperlinks": "^3.2.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": ">=20.0.0" + "node": ">=18.12.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-17.0.0.tgz", + "integrity": "sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.12.0" }, "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } + "stylelint": "^16.23.0" } }, - "node_modules/react-router-dom": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.5.tgz", - "integrity": "sha512-mkEmq/K8tKN63Ae2M7Xgz3c9l9YNbY+NHH6NNeUmLA3kDkhKXRsNb/ZpxaEunvGo2/3YXdk5EJU3Hxp3ocaBPw==", + "node_modules/stylelint-config-standard": { + "version": "39.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-39.0.1.tgz", + "integrity": "sha512-b7Fja59EYHRNOTa3aXiuWnhUWXFU2Nfg6h61bLfAb5GS5fX3LMUD0U5t4S8N/4tpHQg3Acs2UVPR9jy2l1g/3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "license": "MIT", "dependencies": { - "react-router": "7.9.5" + "stylelint-config-recommended": "^17.0.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=18.12.0" }, "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" + "stylelint": "^16.23.0" } }, - "node_modules/recharts": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.3.0.tgz", - "integrity": "sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==", + "node_modules/stylelint-order": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-7.0.0.tgz", + "integrity": "sha512-rSWxx0KscYfxU02wEskKXES9lkRzuuONMMNkZ7SUc6uiF3tDKm7e+sE0Ax/SBlG4TUf1sp1R6f3/SlsPGmzthg==", + "dev": true, "license": "MIT", "dependencies": { - "@reduxjs/toolkit": "1.x.x || 2.x.x", - "clsx": "^2.1.1", - "decimal.js-light": "^2.5.1", - "es-toolkit": "^1.39.3", - "eventemitter3": "^5.0.1", - "immer": "^10.1.1", - "react-redux": "8.x.x || 9.x.x", - "reselect": "5.1.1", - "tiny-invariant": "^1.3.3", - "use-sync-external-store": "^1.2.2", - "victory-vendor": "^37.0.2" + "postcss": "^8.5.3", + "postcss-sorting": "^9.1.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "stylelint": "^16.18.0" } }, - "node_modules/redux": { + "node_modules/stylelint/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true, "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==", + "node_modules/stylelint/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.4.tgz", + "integrity": "sha512-5XRUFc0WTtUbjfGzEwXc42tiGxQHBmtbUG1h9L2apu4SulCGN3Hqm//9D6FAolf8MYNL7f/YlJl9vy08pj5JuA==", + "dev": true, "license": "MIT", - "peerDependencies": { - "redux": "^5.0.0" + "dependencies": { + "flat-cache": "^6.1.13" } }, - "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/stylelint/node_modules/flat-cache": { + "version": "6.1.18", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.18.tgz", + "integrity": "sha512-JUPnFgHMuAVmLmoH9/zoZ6RHOt5n9NlUw/sDXsTbROJ2SFoS2DS4s+swAV6UTeTbGH/CAsZIE6M8TaG/3jVxgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.1.0", + "flatted": "^3.3.3", + "hookified": "^1.12.0" + } }, - "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==", + "node_modules/stylelint/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": ">= 4" } }, - "node_modules/rollup": { - "version": "4.52.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", - "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "node_modules/stylelint/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": 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.5", - "@rollup/rollup-android-arm64": "4.52.5", - "@rollup/rollup-darwin-arm64": "4.52.5", - "@rollup/rollup-darwin-x64": "4.52.5", - "@rollup/rollup-freebsd-arm64": "4.52.5", - "@rollup/rollup-freebsd-x64": "4.52.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", - "@rollup/rollup-linux-arm-musleabihf": "4.52.5", - "@rollup/rollup-linux-arm64-gnu": "4.52.5", - "@rollup/rollup-linux-arm64-musl": "4.52.5", - "@rollup/rollup-linux-loong64-gnu": "4.52.5", - "@rollup/rollup-linux-ppc64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-gnu": "4.52.5", - "@rollup/rollup-linux-riscv64-musl": "4.52.5", - "@rollup/rollup-linux-s390x-gnu": "4.52.5", - "@rollup/rollup-linux-x64-gnu": "4.52.5", - "@rollup/rollup-linux-x64-musl": "4.52.5", - "@rollup/rollup-openharmony-arm64": "4.52.5", - "@rollup/rollup-win32-arm64-msvc": "4.52.5", - "@rollup/rollup-win32-ia32-msvc": "4.52.5", - "@rollup/rollup-win32-x64-gnu": "4.52.5", - "@rollup/rollup-win32-x64-msvc": "4.52.5", - "fsevents": "~2.3.2" + "node": ">=8" } }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/stylelint/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" + "node_modules/stylelint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", "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==", + "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": { - "shebang-regex": "^3.0.0" + "has-flag": "^4.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==", + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, "engines": { - "node": ">=8" + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, - "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==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10.0.0" } }, - "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==", + "node_modules/table/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/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", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/styled-components": { - "version": "6.1.19", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", - "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/table/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, "license": "MIT", "dependencies": { - "@emotion/is-prop-valid": "1.2.2", - "@emotion/unitless": "0.8.1", - "@types/stylis": "4.2.5", - "css-to-react-native": "3.2.0", - "csstype": "3.1.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.2", - "tslib": "2.6.2" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" }, "engines": { - "node": ">= 16" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/styled-components/node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "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" - } - ], + "node_modules/table/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=8" } }, - "node_modules/stylis": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", - "license": "MIT" - }, - "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==", + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" @@ -3487,6 +6688,45 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "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/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -3506,6 +6746,117 @@ "node": ">= 0.8.0" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -3556,6 +6907,13 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/victory-vendor": { "version": "37.3.6", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", @@ -3584,6 +6942,7 @@ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -3669,6 +7028,95 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -3679,6 +7127,69 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -3686,6 +7197,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 336cd49..a73c817 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,33 @@ "private": true, "version": "0.0.0", "type": "module", + "engines": { + "node": "22.18.0" + }, "scripts": { - "dev": "vite", + "dev": "node check-node-version.cjs && vite", "build": "vite build", "lint": "eslint .", - "preview": "vite preview" + "lint:fix": "eslint . --fix", + "stylelint": "stylelint ./src/**/*.style.js", + "stylelint:fix": "stylelint ./src/**/*.style.js --fix", + "lint-front": "lint-staged", + "prettier": "prettier . --write", + "prettier:check": "prettier . --check", + "prepare": "husky" + }, + "lint-staged": { + "src/**/*.{js,jsx}": [ + "eslint --fix", + "prettier --write", + "stylelint --fix" + ] }, "dependencies": { "axios": "^1.13.1", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-error-boundary": "^6.0.0", "react-router-dom": "^7.9.5", "recharts": "^3.3.0", "styled-components": "^6.1.19" @@ -23,10 +40,19 @@ "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", "eslint": "^9.36.0", + "eslint-import-resolver-alias": "^1.1.2", + "eslint-import-resolver-node": "^0.3.9", + "eslint-plugin-import": "^2.32.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", "globals": "^16.4.0", + "husky": "^9.1.7", + "lint-staged": "^16.2.6", + "postcss-styled-syntax": "^0.7.1", "prettier": "^3.6.2", + "stylelint": "^16.25.0", + "stylelint-config-standard": "^39.0.1", + "stylelint-order": "^7.0.0", "vite": "^7.1.7" } } diff --git a/public/placeholder.png b/public/placeholder.png new file mode 100644 index 0000000..d2ba2fe Binary files /dev/null and b/public/placeholder.png differ diff --git a/recommendation-api/main.py b/recommendation-api/main.py new file mode 100644 index 0000000..1f4c722 --- /dev/null +++ b/recommendation-api/main.py @@ -0,0 +1,53 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from typing import List +from models.recommender import IdolRecommender + +app = FastAPI(title="FandomK AI Recommendation API") + +# CORS 설정 (React 앱과 통신) +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # 개발 중에는 모든 origin 허용 + allow_credentials=False, # credentials 비활성화 (와일드카드 사용 시 필수) + allow_methods=["*"], + allow_headers=["*"], +) + +# 전역으로 추천 모델 로드 +recommender = IdolRecommender() + +class RecommendRequest(BaseModel): + selected_idol_ids: List[int] + limit: int = 10 + +class RecommendResponse(BaseModel): + recommended_ids: List[int] + scores: List[float] + +@app.on_event("startup") +async def startup_event(): + """서버 시작 시 아이돌 데이터 로드 및 모델 초기화""" + await recommender.initialize() + print("AI 추천 모델 초기화 완료") + +@app.get("/") +async def root(): + return {"message": "FandomK AI Recommendation API"} + +@app.post("/api/recommend", response_model=RecommendResponse) +async def recommend_idols(request: RecommendRequest): + """선택한 아이돌과 유사한 아이돌 추천""" + recommended_ids, scores = recommender.get_recommendations( + selected_idol_ids=request.selected_idol_ids, + limit=request.limit + ) + return RecommendResponse( + recommended_ids=recommended_ids, + scores=scores + ) + +@app.get("/health") +async def health_check(): + return {"status": "healthy"} \ No newline at end of file diff --git a/recommendation-api/models/__init__.py b/recommendation-api/models/__init__.py new file mode 100644 index 0000000..0b75a14 --- /dev/null +++ b/recommendation-api/models/__init__.py @@ -0,0 +1,2 @@ +# models package + diff --git a/recommendation-api/models/idol_metadata.py b/recommendation-api/models/idol_metadata.py new file mode 100644 index 0000000..32594a2 --- /dev/null +++ b/recommendation-api/models/idol_metadata.py @@ -0,0 +1,42 @@ +# 그룹별 메타데이터 (수동 관리) +GROUP_METADATA = { + "엔하이픈": { + "agency": "빌리프랩", + "genres": ["K-POP", "팝", "댄스", "록", "R&B", "EDM", "랩/힙합"], + "description": "대한민국의 남자 아이돌 그룹이며, 빌리프랩 소속이고, 주요 장르는 K-POP, 팝, 댄스, 록, R&B, EDM, 랩/힙합입니다." + }, + "블랙핑크": { + "agency": "YG", + "genres": ["댄스", "랩/힙합", "팝", "R&B/Soul", "록/메탈", "포크/블루스", "EDM/뭄바톤"], + "description": "대한민국의 여자 아이돌 그룹이며, YG 소속이고, 주요 장르는 댄스, 랩/힙합, 팝, R&B/Soul, 록/메탈, 포크/블루스, EDM/뭄바톤입니다." + }, + "에스파": { + "agency": "SM", + "genres": ["K-POP", "댄스", "힙합", "하이퍼팝", "발라드"], + "description": "대한민국의 여자 아이돌 그룹이며, SM 소속이고, 주요 장르는 K-POP, 댄스, 힙합, 하이퍼팝, 발라드입니다." + }, + "아이브": { + "agency": "스타쉽", + "genres": ["댄스", "팝", "EDM", "R&B", "힙합", "발라드"], + "description": "대한민국의 여자 아이돌 그룹이며, 스타쉽 소속이고, 주요 장르는 댄스, 팝, EDM, R&B, 힙합, 발라드입니다." + }, + "라이즈": { + "agency": "SM", + "genres": ["이모셔널팝"], + "description": "대한민국의 남자 아이돌 그룹이며, SM 소속이고, 주요 장르는 이모셔널팝입니다." + }, + "아스트로": { + "agency": "판타지오", + "genres": ["댄스", "발라드"], + "description": "대한민국의 남자 아이돌 그룹이며, 판타지오 소속이고, 주요 장르는 댄스, 발라드입니다." + }, +} + + +def get_group_description(group_name: str) -> str: + """그룹 이름으로 메타데이터 설명 가져오기""" + metadata = GROUP_METADATA.get(group_name, None) + if metadata: + return metadata["description"] + return "" # 메타데이터 없으면 빈 문자열 + diff --git a/recommendation-api/models/recommender.py b/recommendation-api/models/recommender.py new file mode 100644 index 0000000..83f47fd --- /dev/null +++ b/recommendation-api/models/recommender.py @@ -0,0 +1,164 @@ +from sentence_transformers import SentenceTransformer +import numpy as np +from sklearn.metrics.pairwise import cosine_similarity +import httpx +from typing import List, Tuple +import os +from .idol_metadata import get_group_description + +class IdolRecommender: + def __init__(self): + # 한국어 지원 경량 모델 (캐싱으로 재시작 시 빠름) + print("Sentence Transformer 모델 로딩 중...") + self.model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') + print("모델 로딩 완료") + self.idols_data = [] + self.embeddings = None + self.idol_id_to_idx = {} + #self.embeddings_cache = None # 캐싱용 + + async def initialize(self): + """아이돌 데이터 로드 및 임베딩 생성""" + # 실제 API에서 아이돌 데이터 가져오기 + await self._fetch_idols_data() + + # 아이돌 프로필을 텍스트로 변환 + idol_texts = self._create_idol_descriptions() + + # 텍스트를 벡터로 변환 (임베딩) + self.embeddings = self.model.encode(idol_texts, show_progress_bar=True) + + # ID to Index 매핑 + self.idol_id_to_idx = { + idol['id']: idx + for idx, idol in enumerate(self.idols_data) + } + + async def _fetch_idols_data(self): + """실제 API에서 아이돌 데이터 가져오기""" + api_url = "https://fandom-k-api.vercel.app/20-2/idols" + + try: + async with httpx.AsyncClient() as client: + print(f"API 호출 시작: {api_url}") + response = await client.get(api_url, params={"pageSize": 100}) + print(f"API 응답 상태: {response.status_code}") + + data = response.json() + print(f"API 응답 데이터 키: {list(data.keys())}") + print(f"응답 전체 구조: {data}") + + self.idols_data = data.get('list', []) + print(f"아이돌 데이터 로드 완료: {len(self.idols_data)}명") + + if self.idols_data: + print(f"첫 번째 아이돌 예시: {self.idols_data[0]}") + else: + print(f"ERROR: 'list' 키에 데이터가 없습니다. 전체 응답: {data}") + except Exception as e: + print(f"ERROR: API 호출 실패 - {type(e).__name__}: {e}") + import traceback + traceback.print_exc() + self.idols_data = [] + + def _create_idol_descriptions(self) -> List[str]: + """아이돌 정보를 자연어 문장으로 변환""" + descriptions = [] + for idol in self.idols_data: + # 기본 정보 + gender_kr = "남자" if idol.get('gender') == 'male' else "여자" + name = idol['name'] + group = idol['group'] + + # 그룹 메타데이터 가져오기 + group_desc = get_group_description(group) + + # 메타데이터가 있으면 풍부한 설명, 없으면 기본 설명 + if group_desc: + desc = f"{name} {gender_kr} {group} {group_desc}" + else: + desc = f"{name} {gender_kr} {group}" + + descriptions.append(desc) + + print(f"📝 생성된 설명 샘플 (처음 3개):") + for i, desc in enumerate(descriptions[:3]): + print(f" {i+1}. {desc[:100]}...") # 처음 100자만 출력 + + return descriptions + + def get_recommendations( + self, + selected_idol_ids: List[int], + limit: int = 10 + ) -> Tuple[List[int], List[float]]: + """선택한 아이돌들과 유사한 아이돌 추천""" + + try: + print(f"추천 요청: 선택된 아이돌 IDs = {selected_idol_ids}") + print(f"현재 로드된 아이돌 수: {len(self.idols_data)}") + + if not selected_idol_ids: + print("선택된 아이돌이 없습니다") + return [], [] + + # 데이터 검증 + if not self.idols_data or self.embeddings is None: + print("ERROR: 아이돌 데이터가 초기화되지 않았습니다") + return [], [] + + # 선택한 아이돌들의 임베딩 평균 계산 + selected_indices = [ + self.idol_id_to_idx[idol_id] + for idol_id in selected_idol_ids + if idol_id in self.idol_id_to_idx + ] + + print(f"매칭된 인덱스: {selected_indices}") + print(f"전체 ID 매핑 샘플 (처음 5개): {list(self.idol_id_to_idx.items())[:5]}") + + if not selected_indices: + print(f"ERROR: 선택된 아이돌 ID {selected_idol_ids}가 데이터에 없습니다") + print(f"사용 가능한 ID 예시: {list(self.idol_id_to_idx.keys())[:10]}") + return [], [] + + # 선택한 아이돌들의 임베딩 평균 (사용자 프로필) + user_profile = np.mean(self.embeddings[selected_indices], axis=0) + except Exception as e: + print(f"ERROR in get_recommendations: {type(e).__name__}: {e}") + import traceback + traceback.print_exc() + return [], [] + + # 모든 아이돌과의 유사도 계산 + similarities = cosine_similarity( + [user_profile], + self.embeddings + )[0] + + # 이미 선택한 아이돌 제외 + for idx in selected_indices: + similarities[idx] = -1 + + # 유사도 높은 순으로 정렬 + top_indices = np.argsort(similarities)[::-1][:limit] + + # ID와 점수 반환 + recommended_ids = [ + self.idols_data[idx]['id'] + for idx in top_indices + ] + scores = [float(similarities[idx]) for idx in top_indices] + + # 디버깅: 추천 결과 출력 + print(f"추천 결과 (총 {len(recommended_ids)}개):") + for idx, (idol_id, score) in enumerate(zip(recommended_ids, scores)): + try: + idol = next((x for x in self.idols_data if x['id'] == idol_id), None) + if idol: + gender_kr = "남자" if idol.get('gender') == 'male' else "여자" + print(f" {idx+1}. {idol['name']} {gender_kr} ({idol['group']}) - 유사도: {score:.3f}") + except Exception as e: + print(f" {idx+1}. ID {idol_id} - 에러: {e}") + + return recommended_ids, scores diff --git a/recommendation-api/requirements.txt b/recommendation-api/requirements.txt new file mode 100644 index 0000000..ff6727e --- /dev/null +++ b/recommendation-api/requirements.txt @@ -0,0 +1,8 @@ +fastapi>=0.104.1 +uvicorn>=0.24.0 +sentence-transformers>=2.2.2 +numpy>=1.26.0 +scikit-learn>=1.3.2 +python-dotenv>=1.0.0 +httpx>=0.25.1 +torch>=2.0.0 \ No newline at end of file diff --git a/src/api/chartClient.js b/src/api/chartClient.js new file mode 100644 index 0000000..60aa988 --- /dev/null +++ b/src/api/chartClient.js @@ -0,0 +1,16 @@ +import client from "./client"; +import PATCHS from "./path"; + +/** + * @param {{ + * gender: "male" | "female", + * cursor?: number, + * pageSize?: number, + * }} params + */ +export const getChartList = async (params) => { + const response = await client.get(`${PATCHS.charts}/${params.gender}`, { + params, + }); + return response.data; +}; diff --git a/src/api/client.js b/src/api/client.js new file mode 100644 index 0000000..13d02d3 --- /dev/null +++ b/src/api/client.js @@ -0,0 +1,17 @@ +import { showToast } from "@/components/common/Toast"; +import axios from "axios"; + +const client = axios.create({ + timeout: 3000, + baseURL: "https://fandom-k-api.vercel.app/20-2", +}); + +client.interceptors.response.use( + (res) => res, + (error) => { + showToast("요청을 처리하는 중 문제가 발생했어요. 잠시 후 다시 시도해 주세요."); + return Promise.reject(error); + } +); + +export default client; diff --git a/src/api/donationsClinet.js b/src/api/donationsClinet.js new file mode 100644 index 0000000..9327279 --- /dev/null +++ b/src/api/donationsClinet.js @@ -0,0 +1,25 @@ +import client from "./client"; +import PATCHS from "./path"; + +/** + * @param {{ + * priorityIdolIds?: number[], + * cursor?: number, + * pageSize?: number, + * }} params + */ +export const getDonationList = async (params) => { + const response = await client.get(PATCHS.donations, { + params, + }); + return response.data; +}; + +/** + * @param { number } id // 후원 id + * @param {{ amount: number }} body // 후원 데이터 + */ +export const contributeDonation = async (id, body) => { + const response = await client.put(`${PATCHS.donations}/${id}/contribute`, body); + return response.data; +}; diff --git a/src/api/idolsClient.js b/src/api/idolsClient.js new file mode 100644 index 0000000..1cd519a --- /dev/null +++ b/src/api/idolsClient.js @@ -0,0 +1,14 @@ +import client from "./client"; +import PATCHS from "./path"; + +/** + * @param {{ + * keyword?: string, + * cursor?: number, + * pageSize?: number, + * }} params + */ +export const getIdolList = async (params) => { + const response = await client.get(PATCHS.idols, { params }); + return response.data; +}; diff --git a/src/api/path.js b/src/api/path.js new file mode 100644 index 0000000..89ff92f --- /dev/null +++ b/src/api/path.js @@ -0,0 +1,7 @@ +const PATCHS = Object.freeze({ + idols: "/idols", + donations: "/donations", + charts: "/charts", +}); + +export default PATCHS; diff --git a/src/api/recommendationClient.js b/src/api/recommendationClient.js new file mode 100644 index 0000000..c9d5a05 --- /dev/null +++ b/src/api/recommendationClient.js @@ -0,0 +1,23 @@ +import axios from "axios"; + +// AI 추천 API 클라이언트 +const recommendationClient = axios.create({ + baseURL: import.meta.env.VITE_AI_API_URL || "https://dayeoni-ai-recommendation.hf.space", + timeout: 30000, // AI 모델 처리 시간 고려해서 30초로 증가 +}); + +/** + * 선택한 아이돌과 유사한 아이돌 추천 + * @param {number[]} selectedIdolIds - 선택한 아이돌 ID 배열 + * @param {number} limit - 추천받을 아이돌 수 + * @returns {Promise<{recommended_ids: number[], scores: number[]}>} + */ +export const getRecommendations = async (selectedIdolIds, limit = 10) => { + const response = await recommendationClient.post("/api/recommend", { + selected_idol_ids: selectedIdolIds, + limit: limit, + }); + return response.data; +}; + +export default recommendationClient; diff --git a/src/app/AppRouter.jsx b/src/app/AppRouter.jsx index 64d46c6..00a6292 100644 --- a/src/app/AppRouter.jsx +++ b/src/app/AppRouter.jsx @@ -1,5 +1,8 @@ -import Home from "@/pages/Home"; -import List from "@/pages/List"; +import Homepage from "@/pages/HomePage"; +import ListPage from "@/pages/ListPage"; +import Mypage from "@/pages/MyPage"; +import NotFoundPage from "@/pages/NotFoundPage"; +import RouteErrorPage from "@/pages/RouteErrorPage"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import RootLayout from "./RootLayout"; @@ -12,10 +15,12 @@ const PATHS = { const router = createBrowserRouter([ { element: , + errorElement: , children: [ - { index: true, element: }, - { path: PATHS.LIST, element: }, - { path: "*", element:
404 에러 쵸비상
}, + { index: true, element: }, + { path: PATHS.LIST, element: }, + { path: PATHS.MYPAGE, element: }, + { path: "*", element: }, ], }, ]); diff --git a/src/app/RootLayout.jsx b/src/app/RootLayout.jsx index 93da4bb..dd5520b 100644 --- a/src/app/RootLayout.jsx +++ b/src/app/RootLayout.jsx @@ -1,9 +1,13 @@ +import { Toast } from "@/components/common/Toast"; +import Header from "@/components/layout/Header"; import { Outlet } from "react-router-dom"; const RootLayout = () => { return ( <> - 나는 루트 레이아웃임 +
+ + ); }; diff --git a/src/app/contexts/CreditContext.jsx b/src/app/contexts/CreditContext.jsx new file mode 100644 index 0000000..a2b20e6 --- /dev/null +++ b/src/app/contexts/CreditContext.jsx @@ -0,0 +1,47 @@ +import { creditStorage } from "@/storage/credit.storage"; +import { createContext, useContext, useEffect, useState } from "react"; + +const CreditContext = createContext(null); + +export const CreditProvider = ({ children }) => { + const [credit, setCredit] = useState(() => creditStorage.get()); + + useEffect(() => { + creditStorage.set(credit); + }, [credit]); + + /** 크레딧 초기화 */ + const resetCredit = () => { + setCredit(0); + }; + + /** 유효성 검사: 차감 가능한지 여부 */ + const isEnoughCredit = (num) => { + return credit - num >= 0; + }; + + /** 크레딧 추가 */ + const addCredit = (num) => { + setCredit((prev) => prev + num); + }; + + /** 크레딧 차감 */ + const subtractCredit = (num) => { + if (!isEnoughCredit(num)) return; + setCredit((prev) => prev - num); + }; + + const actions = { resetCredit, isEnoughCredit, addCredit, subtractCredit }; + + return {children}; +}; + +const useCreditContext = () => { + const context = useContext(CreditContext); + if (!context) { + throw new Error("useCreditContext must be used within a CreditProvider"); + } + return context; +}; + +export default useCreditContext; diff --git a/src/assets/Chart.png b/src/assets/Chart.png new file mode 100644 index 0000000..9df76df Binary files /dev/null and b/src/assets/Chart.png differ diff --git a/src/assets/btn_delete_24px.png b/src/assets/btn_delete_24px.png new file mode 100644 index 0000000..d09ed97 Binary files /dev/null and b/src/assets/btn_delete_24px.png differ diff --git a/src/assets/credit_113px.png b/src/assets/credit_113px.png new file mode 100644 index 0000000..b47273e Binary files /dev/null and b/src/assets/credit_113px.png differ diff --git a/src/assets/icj_arrow_left.png b/src/assets/icj_arrow_left.png new file mode 100644 index 0000000..48d169b Binary files /dev/null and b/src/assets/icj_arrow_left.png differ diff --git a/src/assets/icj_arrow_left2.png b/src/assets/icj_arrow_left2.png new file mode 100644 index 0000000..f48e9fc Binary files /dev/null and b/src/assets/icj_arrow_left2.png differ diff --git a/src/assets/imgs/credit.png b/src/assets/imgs/credit.png new file mode 100644 index 0000000..903e775 Binary files /dev/null and b/src/assets/imgs/credit.png differ diff --git a/src/assets/imgs/header_glow.png b/src/assets/imgs/header_glow.png new file mode 100644 index 0000000..9991063 Binary files /dev/null and b/src/assets/imgs/header_glow.png differ diff --git a/src/assets/imgs/home1.png b/src/assets/imgs/home1.png new file mode 100644 index 0000000..bfc3c46 Binary files /dev/null and b/src/assets/imgs/home1.png differ diff --git a/src/assets/imgs/home2.png b/src/assets/imgs/home2.png new file mode 100644 index 0000000..a31c62e Binary files /dev/null and b/src/assets/imgs/home2.png differ diff --git a/src/assets/imgs/home3.png b/src/assets/imgs/home3.png new file mode 100644 index 0000000..dbf8543 Binary files /dev/null and b/src/assets/imgs/home3.png differ diff --git a/src/assets/imgs/home4.png b/src/assets/imgs/home4.png new file mode 100644 index 0000000..d751029 Binary files /dev/null and b/src/assets/imgs/home4.png differ diff --git a/src/assets/imgs/home_monthly_bg.png b/src/assets/imgs/home_monthly_bg.png new file mode 100644 index 0000000..dbf8543 Binary files /dev/null and b/src/assets/imgs/home_monthly_bg.png differ diff --git a/src/assets/imgs/home_monthly_phone.png b/src/assets/imgs/home_monthly_phone.png new file mode 100644 index 0000000..d5ffea0 Binary files /dev/null and b/src/assets/imgs/home_monthly_phone.png differ diff --git a/src/assets/imgs/home_my_artist_bg.png b/src/assets/imgs/home_my_artist_bg.png new file mode 100644 index 0000000..d751029 Binary files /dev/null and b/src/assets/imgs/home_my_artist_bg.png differ diff --git a/src/assets/imgs/home_my_artist_phone.png b/src/assets/imgs/home_my_artist_phone.png new file mode 100644 index 0000000..3ecc2b0 Binary files /dev/null and b/src/assets/imgs/home_my_artist_phone.png differ diff --git a/src/assets/imgs/home_sponsor_bg.png b/src/assets/imgs/home_sponsor_bg.png new file mode 100644 index 0000000..a31c62e Binary files /dev/null and b/src/assets/imgs/home_sponsor_bg.png differ diff --git a/src/assets/imgs/home_sponsor_phone.png b/src/assets/imgs/home_sponsor_phone.png new file mode 100644 index 0000000..bd558b1 Binary files /dev/null and b/src/assets/imgs/home_sponsor_phone.png differ diff --git a/src/assets/imgs/home_start_bg.png b/src/assets/imgs/home_start_bg.png new file mode 100644 index 0000000..bfc3c46 Binary files /dev/null and b/src/assets/imgs/home_start_bg.png differ diff --git a/src/assets/imgs/logo.svg b/src/assets/imgs/logo.svg new file mode 100644 index 0000000..a3e1974 --- /dev/null +++ b/src/assets/imgs/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/imgs/phone2-1.png b/src/assets/imgs/phone2-1.png new file mode 100644 index 0000000..bd558b1 Binary files /dev/null and b/src/assets/imgs/phone2-1.png differ diff --git a/src/assets/imgs/phone3-2.png b/src/assets/imgs/phone3-2.png new file mode 100644 index 0000000..d5ffea0 Binary files /dev/null and b/src/assets/imgs/phone3-2.png differ diff --git a/src/assets/imgs/phone4-3.png b/src/assets/imgs/phone4-3.png new file mode 100644 index 0000000..3ecc2b0 Binary files /dev/null and b/src/assets/imgs/phone4-3.png differ diff --git a/src/assets/imgs/profile.png b/src/assets/imgs/profile.png new file mode 100644 index 0000000..2873582 Binary files /dev/null and b/src/assets/imgs/profile.png differ diff --git a/src/assets/svg/ArrowSvg.jsx b/src/assets/svg/ArrowSvg.jsx new file mode 100644 index 0000000..1346388 --- /dev/null +++ b/src/assets/svg/ArrowSvg.jsx @@ -0,0 +1,21 @@ +const ArrowSvg = ({ size = 24, color = "white" }) => { + return ( + + + + ); +}; + +export default ArrowSvg; diff --git a/src/assets/svg/CloseButtonSvg.jsx b/src/assets/svg/CloseButtonSvg.jsx new file mode 100644 index 0000000..9a8e620 --- /dev/null +++ b/src/assets/svg/CloseButtonSvg.jsx @@ -0,0 +1,21 @@ +const CloseButtonSvg = ({ size = "24px", onClick }) => { + return ( + onClick()} + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + + + ); +}; + +export default CloseButtonSvg; diff --git a/src/assets/svg/CreditSvg.jsx b/src/assets/svg/CreditSvg.jsx new file mode 100644 index 0000000..440e657 --- /dev/null +++ b/src/assets/svg/CreditSvg.jsx @@ -0,0 +1,67 @@ +const CreditSvg = ({ size = "28px" }) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default CreditSvg; diff --git a/src/assets/svg/CreditWhiteSvg.jsx b/src/assets/svg/CreditWhiteSvg.jsx new file mode 100644 index 0000000..c0b7985 --- /dev/null +++ b/src/assets/svg/CreditWhiteSvg.jsx @@ -0,0 +1,70 @@ +const CreditWhite = ({ size = "25px" }) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default CreditWhite; diff --git a/src/assets/svg/MypageAddSvg.jsx b/src/assets/svg/MypageAddSvg.jsx new file mode 100644 index 0000000..80feab3 --- /dev/null +++ b/src/assets/svg/MypageAddSvg.jsx @@ -0,0 +1,16 @@ +const MypageAdd = ({ size = 24, strokeColor = "white", strokeWidth = 2 }) => { + return ( + + + + + ); +}; + +export default MypageAdd; diff --git a/src/assets/svg/MypageArrowSvg.jsx b/src/assets/svg/MypageArrowSvg.jsx new file mode 100644 index 0000000..24bf9fd --- /dev/null +++ b/src/assets/svg/MypageArrowSvg.jsx @@ -0,0 +1,26 @@ +const MypageArrow = ({ + width = 8, + height = 14, + strokeColor = "white", + strokeWidth = 2, + strokeLinecap = "round", +}) => { + return ( + + + + ); +}; + +export default MypageArrow; diff --git a/src/assets/svg/MypageDeleteSvg.jsx b/src/assets/svg/MypageDeleteSvg.jsx new file mode 100644 index 0000000..4c3aca9 --- /dev/null +++ b/src/assets/svg/MypageDeleteSvg.jsx @@ -0,0 +1,16 @@ +const MypageDelete = () => { + return ( + + + + ); +}; + +export default MypageDelete; diff --git a/src/assets/svg/RadioOffSvg.jsx b/src/assets/svg/RadioOffSvg.jsx new file mode 100644 index 0000000..ed7508d --- /dev/null +++ b/src/assets/svg/RadioOffSvg.jsx @@ -0,0 +1,16 @@ +const RadioOffSvg = ({ size = "16px" }) => { + return ( + + + + + ); +}; + +export default RadioOffSvg; diff --git a/src/assets/svg/RadioOnSvg.jsx b/src/assets/svg/RadioOnSvg.jsx new file mode 100644 index 0000000..517144c --- /dev/null +++ b/src/assets/svg/RadioOnSvg.jsx @@ -0,0 +1,16 @@ +const RadioOnSvg = ({ size = "16px", color = "#F96D69" }) => { + return ( + + + + + ); +}; + +export default RadioOnSvg; diff --git a/src/components/common/ArrowButton.jsx b/src/components/common/ArrowButton.jsx new file mode 100644 index 0000000..c852f13 --- /dev/null +++ b/src/components/common/ArrowButton.jsx @@ -0,0 +1,26 @@ +import ArrowSvg from "@/assets/svg/ArrowSvg"; +import * as S from "./ArrowButton.style"; + +const ArrowButton = ({ + onClick, + label, + disabled, + $size = "md", + $isLeft = true, + $isTablet = true, +}) => { + return ( + + + + ); +}; + +export default ArrowButton; diff --git a/src/components/common/ArrowButton.style.js b/src/components/common/ArrowButton.style.js new file mode 100644 index 0000000..609d6b1 --- /dev/null +++ b/src/components/common/ArrowButton.style.js @@ -0,0 +1,51 @@ +import { media } from "@/styles/media"; +import { hexToRgba } from "@/utils/color"; +import styled, { css } from "styled-components"; + +const sizes = { + md: css` + width: 40px; + height: 80px; + `, + lg: css` + width: 29px; + height: 135px; + `, +}; + +export const ArrowButtonWrapper = styled.button` + display: none; + cursor: pointer; + border-radius: 8px; + background-color: ${hexToRgba("#1b1b1bcc")}; + + ${({ $isLeft }) => + $isLeft ? { left: "-80px" } : { right: "-80px", transform: "rotate(180deg)" }} + + ${({ $size }) => sizes[$size] || sizes.md} + + &:disabled { + background: ${hexToRgba("#080611")}; + } + + @media ${media.tablet} { + ${({ $isTablet }) => ($isTablet ? { display: "block" } : {})} + } + + @media ${media.desktop} { + display: block; + } + + @media ${media.desktopSlider} { + position: absolute; + top: 50%; + ${({ $isLeft }) => + $isLeft + ? css` + transform: translateY(-50%); + ` + : css` + transform: translateY(-50%) rotate(180deg); + `} + } +`; diff --git a/src/components/common/HighlightButton.jsx b/src/components/common/HighlightButton.jsx new file mode 100644 index 0000000..c84d182 --- /dev/null +++ b/src/components/common/HighlightButton.jsx @@ -0,0 +1,11 @@ +import * as S from "./HighlightButton.style"; + +const HighlightButton = ({ children, disabled, onClick, $customStyle, type }) => { + return ( + + {children} + + ); +}; + +export default HighlightButton; diff --git a/src/components/common/HighlightButton.style.js b/src/components/common/HighlightButton.style.js new file mode 100644 index 0000000..ea133c9 --- /dev/null +++ b/src/components/common/HighlightButton.style.js @@ -0,0 +1,21 @@ +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Button = styled.button` + ${TYPO.body14Bold} + border-radius: 4px; + background: linear-gradient( + to right, + ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}, + ${hexToRgba(COLOR_VAR_MAP["--color-secondary"])} + ); + cursor: pointer; + + &:disabled { + cursor: not-allowed; + background: var(--color-gray-500); + } + + ${({ $customStyle }) => $customStyle && $customStyle}; +`; diff --git a/src/components/common/IdolCircleImage.jsx b/src/components/common/IdolCircleImage.jsx new file mode 100644 index 0000000..3423a4c --- /dev/null +++ b/src/components/common/IdolCircleImage.jsx @@ -0,0 +1,13 @@ +import * as S from "./IdolCircleImage.style"; + +const IdolCircleImage = ({ src, alt, $size = "md" }) => { + return ( + + + + + + ); +}; + +export default IdolCircleImage; diff --git a/src/components/common/IdolCircleImage.style.js b/src/components/common/IdolCircleImage.style.js new file mode 100644 index 0000000..1636cb5 --- /dev/null +++ b/src/components/common/IdolCircleImage.style.js @@ -0,0 +1,38 @@ +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const IMAGE_SIZES = { + sm: 60, + md: 86, + lg: 115, +}; + +// wrapper 사이즈 (이미지 + 여백) +export const WRAPPER_SIZES = { + sm: 70, + md: 100, + lg: 128, +}; + +export const IdolCircleImageBorder = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: ${({ $size }) => WRAPPER_SIZES[$size]}px; + aspect-ratio: 1 / 1; + border: 1px solid ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}; + border-radius: 999px; +`; + +export const IdolCircleImageWrapper = styled.div` + width: ${({ $size }) => IMAGE_SIZES[$size]}px; + aspect-ratio: 1 / 1; + overflow: hidden; + border-radius: 999px; +`; + +export const IdolImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; +`; diff --git a/src/components/common/Modal.jsx b/src/components/common/Modal.jsx new file mode 100644 index 0000000..402c2c1 --- /dev/null +++ b/src/components/common/Modal.jsx @@ -0,0 +1,21 @@ +import { createPortal } from "react-dom"; +import * as S from "./Modal.style"; +import CloseButtonSvg from "@/assets/svg/CloseButtonSvg"; + +const Modal = ({ title, isOpen, onClose, children }) => { + if (!isOpen) return null; + return createPortal( + onClose()}> + e.stopPropagation()}> + + {title} + onClose()} /> + + {children} + + , + document.getElementById("modal-root") + ); +}; + +export default Modal; diff --git a/src/components/common/Modal.style.js b/src/components/common/Modal.style.js new file mode 100644 index 0000000..673b79a --- /dev/null +++ b/src/components/common/Modal.style.js @@ -0,0 +1,35 @@ +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + position: fixed; + inset: 0; + justify-content: center; + align-items: center; + + background-color: ${hexToRgba("#000c")}; +`; + +export const ModalContainer = styled.div` + padding: 24px 16px 32px; + + background-color: var(--color-bg-base); + border-radius: 8px; +`; + +export const TitleBar = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + + & > svg { + cursor: pointer; + } +`; + +export const ModalTitle = styled.div` + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}; + ${TYPO.title18SemiBold} +`; diff --git a/src/components/common/SectionErrorBoundary.jsx b/src/components/common/SectionErrorBoundary.jsx new file mode 100644 index 0000000..5fa33b7 --- /dev/null +++ b/src/components/common/SectionErrorBoundary.jsx @@ -0,0 +1,36 @@ +import { ErrorBoundary } from "react-error-boundary"; +import * as S from "./SectionErrorBoundary.style"; + +const SectionErrorFallback = ({ error, resetErrorBoundary }) => { + // 에러 메시지 정제 + const message = + error?.response?.status === 504 + ? "서버 응답이 지연되어 요청을 처리할 수 없어요. (504 Gateway Timeout)" + : error?.message || "요청을 처리하는 중 문제가 발생했어요."; + + return ( + + ⚠️ 로딩 중 오류 발생 + {message} + + 다시 시도해 주세요. 문제가 반복되면 관리자에게 문의 부탁드립니다. + + 다시 시도하기 + + ); +}; + +const SectionErrorBoundary = ({ children }) => { + return ( + { + console.error("[SectionErrorBoundary]", error, info); + }} + > + {children} + + ); +}; + +export default SectionErrorBoundary; diff --git a/src/components/common/SectionErrorBoundary.style.js b/src/components/common/SectionErrorBoundary.style.js new file mode 100644 index 0000000..6350db6 --- /dev/null +++ b/src/components/common/SectionErrorBoundary.style.js @@ -0,0 +1,54 @@ +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Container = styled.div` + width: 100%; + padding: 24px; + border-radius: 12px; + background: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"], 0.05)}; + border: 1px solid ${hexToRgba(COLOR_VAR_MAP["--color-white-100"], 0.1)}; + text-align: center; + margin: 16px 0; +`; + +export const Title = styled.h3` + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; + font-size: 20px; + font-weight: 700; + margin-bottom: 12px; +`; + +export const Message = styled.p` + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}; + font-size: 16px; + margin-bottom: 12px; + line-height: 1.5; +`; + +export const Description = styled.p` + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}; + font-size: 14px; + margin-bottom: 20px; + line-height: 1.6; +`; + +export const RetryButton = styled.button` + padding: 10px 18px; + border-radius: 8px; + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}; + font-size: 14px; + font-weight: 700; + border: none; + cursor: pointer; + transition: 0.2s; + background: linear-gradient( + to right, + ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}, + ${hexToRgba(COLOR_VAR_MAP["--color-secondary"])} + ); + + &:hover { + opacity: 0.9; + transform: translateY(-2px); + } +`; diff --git a/src/components/common/Toast.jsx b/src/components/common/Toast.jsx new file mode 100644 index 0000000..3fe76c9 --- /dev/null +++ b/src/components/common/Toast.jsx @@ -0,0 +1,74 @@ +import { createPortal } from "react-dom"; +import * as S from "./Toast.style"; +import { useEffect, useState } from "react"; + +export const Toast = () => { + const [toasts, setToasts] = useState([]); + + const onToast = (e) => { + console.log(e.detail, e); + setToasts((prev) => [...prev, e.detail]); + }; + + useEffect(() => { + document.addEventListener("toast", onToast); + + return () => { + document.removeEventListener("toast", onToast); + }; + }, []); + + const onClose = () => { + setToasts((prev) => prev.slice(1)); + }; + + if (toasts.length == 0) return null; + const item = toasts[0]; + return createPortal( + , + document.getElementById("toast-root") + ); +}; + +const ToastItem = ({ item, onClose }) => { + const { title, message } = item; + const [isToasting, setIsToasting] = useState(false); + const [isClosing, setIsClosing] = useState(false); + + useEffect(() => { + setIsToasting(true); + setIsClosing(false); + + setTimeout(() => { + setIsToasting(false); + setIsClosing(true); + }, 3000); + }, [item]); + + useEffect(() => { + if (!isClosing) return; + + setTimeout(() => { + onClose(); + }, 500); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isClosing]); + + return ( + + {title} + {message} + + ); +}; + +export const showToast = (title, message) => { + const event = new CustomEvent("toast", { + bubbles: false, + detail: { + title, + message, + }, + }); + document.dispatchEvent(event); +}; diff --git a/src/components/common/Toast.style.js b/src/components/common/Toast.style.js new file mode 100644 index 0000000..9b6d582 --- /dev/null +++ b/src/components/common/Toast.style.js @@ -0,0 +1,29 @@ +import { TYPO } from "@/styles/typography"; +import { hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const ToastContainer = styled.div` + position: fixed; + right: 20px; + bottom: 20px; + z-index: 2000; + padding: 15px; + + background-color: ${hexToRgba("#fffc")}; + max-width: 200px; + border-radius: 8px; + pointer-events: none; + transition: all 0.5s; + opacity: ${({ isToasting }) => (isToasting ? 1 : 0.1)}; + transform: translateY(${({ isToasting }) => (isToasting ? 0 : 100)}%); +`; + +export const ToastTitle = styled.h2` + color: black; + ${TYPO.caption12SemiBold} +`; + +export const ToastMessage = styled.p` + color: ${hexToRgba("#333")}; + ${TYPO.body14Medium} +`; diff --git a/src/components/layout/Header.jsx b/src/components/layout/Header.jsx new file mode 100644 index 0000000..f502f4e --- /dev/null +++ b/src/components/layout/Header.jsx @@ -0,0 +1,30 @@ +import LogoImg from "../../assets/imgs/logo.svg"; +import ProfileImg from "../../assets/imgs/profile.png"; +import GlowImg from "../../assets/imgs/header_glow.png"; +import { useLocation, useNavigate } from "react-router-dom"; +import * as S from "./Header.style"; + +const Header = () => { + const navigate = useNavigate(); + const location = useLocation(); + if (location.pathname == "/") return null; + + return ( + + + + + { + navigate("/list"); + }} + /> + navigate("/mypage")} /> + + + ); +}; + +export default Header; diff --git a/src/components/layout/Header.style.js b/src/components/layout/Header.style.js new file mode 100644 index 0000000..4ef668e --- /dev/null +++ b/src/components/layout/Header.style.js @@ -0,0 +1,70 @@ +import { media } from "@/styles/media"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const HeaderContainer = styled.div` + width: 100%; + height: 64px; + + background-color: ${hexToRgba(COLOR_VAR_MAP["--color-bg-dark"])}; + + @media ${media.desktop} { + height: 80px; + } +`; + +export const HeaderGlow = styled.img` + position: absolute; + top: -49px; + left: -114px; + width: 199px; + height: 273px; +`; + +export const HeaderContent = styled.div` + position: relative; + z-index: 1; + width: 100%; + height: 100%; + margin: 0 auto; + + @media ${media.desktop} { + width: 1200px; + } +`; + +export const HeaderLogo = styled.img` + position: absolute; + top: 50%; + left: 50%; + width: 108px; + height: 20.58px; + transform: translate(-50%, -50%); + cursor: pointer; + + @media ${media.tablet} { + width: 120px; + height: 22.87px; + } + + @media ${media.desktop} { + width: 167.92px; + height: 32px; + } +`; + +export const HeaderProfile = styled.img` + position: absolute; + top: 50%; + right: 24px; + width: 32px; + height: 32px; + border-radius: 50%; + object-fit: cover; + transform: translateY(-50%); + cursor: pointer; + + @media ${media.desktop} { + right: 0; + } +`; diff --git a/src/hooks/useDraggableSlider.js b/src/hooks/useDraggableSlider.js new file mode 100644 index 0000000..66a1fd8 --- /dev/null +++ b/src/hooks/useDraggableSlider.js @@ -0,0 +1,209 @@ +import { useCallback, useEffect, useRef, useState } from "react"; + +/** + * useDraggableSlider 훅 + * + * 리스트를 좌우로 드래그하거나 버튼으로 이동할 수 있는 슬라이더 UI를 만들기 위한 훅입니다. + * 드래그 중에는 transition 없이 즉각적으로 움직이고, + * 버튼 이동 시에는 transition 애니메이션이 적용됩니다. + * + * @param {number} trigger + * 슬라이더 내부 요소의 개수 혹은 렌더링 변화 감지용 값. + * trigger가 변경되면 슬라이더의 크기를 다시 계산합니다. + * + * @returns {{ + * viewportRef: React.RefObject; + * listRef: React.RefObject; + * hasPrev: boolean; + * hasNext: boolean; + * handlePrev: () => void; + * handleNext: () => void; + * handleMouseDown: (e: React.MouseEvent) => void; + * handleTouchStart: (e: React.TouchEvent) => void; + * handleTouchMove: (e: React.TouchEvent) => void; + * handleTouchEnd: () => void; + * }} + * + * @property {React.RefObject} viewportRef + * viewport 영역의 ref. 실제 가시 영역을 나타냅니다. + * + * @property {React.RefObject} listRef + * 전체 슬라이드 리스트를 감싸는 요소의 ref. + * + * @property {boolean} hasPrev + * 이전 슬라이드로 이동이 가능한지 여부. + * + * @property {boolean} hasNext + * 다음 슬라이드로 이동이 가능한지 여부. + * + * @property {Function} handlePrev + * 이전 슬라이드로 이동시키는 버튼 핸들러. + * + * @property {Function} handleNext + * 다음 슬라이드로 이동시키는 버튼 핸들러. + * + * @property {Function} handleMouseDown + * 마우스를 이용한 드래그 시작 핸들러. + * + * @property {Function} handleTouchStart + * 모바일 터치 드래그 시작 핸들러. + * + * @property {Function} handleTouchMove + * 모바일 터치 드래그 이동 핸들러. + * + * @property {Function} handleTouchEnd + * 모바일 터치 드래그 종료 핸들러. + */ +const useDraggableSlider = (trigger) => { + const viewportRef = useRef(null); + const listRef = useRef(null); + + const [sizes, setSizes] = useState({ + viewportWidth: 0, + listWidth: 0, + maxOffset: 0, + }); + + const isDraggingRef = useRef(false); + const startXRef = useRef(0); + const startOffsetRef = useRef(0); + const offsetRef = useRef(0); + + const [offsetState, setOffsetState] = useState(0); + + const clampOffset = useCallback( + (value) => Math.min(Math.max(value, 0), sizes.maxOffset), + [sizes.maxOffset] + ); + + const applyTransform = useCallback((withTransition) => { + if (!listRef.current) return; + listRef.current.style.transition = withTransition ? "transform 0.3s ease" : "none"; + listRef.current.style.transform = `translateX(-${offsetRef.current}px)`; + }, []); + + const measureSizes = useCallback(() => { + if (!viewportRef.current || !listRef.current) return; + + const viewportWidth = viewportRef.current.getBoundingClientRect().width; + const listWidth = listRef.current.scrollWidth; + const maxOffset = Math.max(listWidth - viewportWidth, 0); + + setSizes({ viewportWidth, listWidth, maxOffset }); + + offsetRef.current = Math.min(offsetRef.current, maxOffset); + setOffsetState(offsetRef.current); + applyTransform(false); // 초기 세팅은 애니메이션 X + }, [applyTransform]); + + useEffect(() => { + measureSizes(); + }, [trigger, measureSizes]); + + useEffect(() => { + window.addEventListener("resize", measureSizes); + return () => window.removeEventListener("resize", measureSizes); + }, [measureSizes]); + + // --- 드래그 --- + const startDrag = useCallback((clientX) => { + isDraggingRef.current = true; + startXRef.current = clientX; + startOffsetRef.current = offsetRef.current; + }, []); + + const moveDrag = useCallback( + (clientX) => { + if (!isDraggingRef.current) return; + const deltaX = clientX - startXRef.current; + const nextOffset = clampOffset(startOffsetRef.current - deltaX); + offsetRef.current = nextOffset; + applyTransform(false); // 드래그 중: transition 없음 + }, + [applyTransform, clampOffset] + ); + + const endDrag = useCallback(() => { + if (!isDraggingRef.current) return; + isDraggingRef.current = false; + setOffsetState(offsetRef.current); // 최종 위치만 state에 반영 + }, []); + + // --- 버튼 이동 --- + const handlePrev = useCallback(() => { + const next = clampOffset(offsetRef.current - sizes.viewportWidth); + offsetRef.current = next; + setOffsetState(next); + applyTransform(true); // 버튼: transition O + }, [applyTransform, clampOffset, sizes.viewportWidth]); + + const handleNext = useCallback(() => { + const next = clampOffset(offsetRef.current + sizes.viewportWidth); + offsetRef.current = next; + setOffsetState(next); + applyTransform(true); // 버튼: transition O + }, [applyTransform, clampOffset, sizes.viewportWidth]); + + const hasPrev = offsetState > 0; + const hasNext = offsetState < sizes.maxOffset; + + // --- 마우스 이벤트 --- + const handleMouseDown = useCallback( + (e) => { + e.preventDefault(); + startDrag(e.clientX); + + const handleMouseMove = (moveEvent) => { + moveDrag(moveEvent.clientX); + }; + + const handleMouseUp = () => { + endDrag(); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + }; + + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + }, + [startDrag, moveDrag, endDrag] + ); + + // --- 터치 이벤트 --- + const handleTouchStart = useCallback( + (e) => { + const touch = e.touches[0]; + if (!touch) return; + startDrag(touch.clientX); + }, + [startDrag] + ); + + const handleTouchMove = useCallback( + (e) => { + const touch = e.touches[0]; + if (!touch) return; + moveDrag(touch.clientX); + }, + [moveDrag] + ); + + const handleTouchEnd = useCallback(() => { + endDrag(); + }, [endDrag]); + + return { + viewportRef, + listRef, + hasPrev, + hasNext, + handlePrev, + handleNext, + handleMouseDown, + handleTouchStart, + handleTouchMove, + handleTouchEnd, + }; +}; + +export default useDraggableSlider; diff --git a/src/hooks/useIntersectionObserver.js b/src/hooks/useIntersectionObserver.js new file mode 100644 index 0000000..7e2e5f6 --- /dev/null +++ b/src/hooks/useIntersectionObserver.js @@ -0,0 +1,55 @@ +import { useEffect, useRef } from "react"; + +/** + * IntersectionObserver 감지 훅 + * @param {{ + * rootRef?: React.RefObject | null; + * onIntersect: (entry: IntersectionObserverEntry) => void; + * threshold?: number | number[]; + * rootMargin?: string; + * enabled?: boolean; + * }} options + */ +const useIntersectionObserver = ({ + rootRef = null, + onIntersect, + threshold = 0.5, + rootMargin = "0px", + enabled = true, +}) => { + const targetRef = useRef(null); + + useEffect(() => { + if (!enabled) return; + if (!onIntersect) return; + + const root = rootRef?.current ?? null; + const target = targetRef.current; + if (!target) return; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + onIntersect(entry); + } + }); + }, + { + root, + threshold, + rootMargin, + } + ); + + observer.observe(target); + + return () => { + observer.disconnect(); + }; + }, [rootRef, onIntersect, threshold, rootMargin, enabled]); + + return targetRef; +}; + +export default useIntersectionObserver; diff --git a/src/hooks/useModal.js b/src/hooks/useModal.js new file mode 100644 index 0000000..1b3eb64 --- /dev/null +++ b/src/hooks/useModal.js @@ -0,0 +1,50 @@ +import { useCallback, useEffect, useState } from "react"; + +const useModal = (initialOpen = false) => { + const [isOpen, setIsOpen] = useState(initialOpen); + const [modalContent, setModalContent] = useState(null); + + const onOpen = useCallback(() => { + setIsOpen(true); + }, []); + + const onClose = useCallback(() => { + setIsOpen(false); + setModalContent(null); + }, []); + + const toggle = useCallback(() => { + setIsOpen((prev) => !prev); + }, []); + + // ESC 키로 닫기 + body 스크롤 잠금 + useEffect(() => { + if (!isOpen) return; + + const handleKeyDown = (e) => { + if (e.key === "Escape") { + onClose(); + } + }; + + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + document.addEventListener("keydown", handleKeyDown); + + return () => { + document.body.style.overflow = prevOverflow; + document.removeEventListener("keydown", handleKeyDown); + }; + }, [isOpen, onClose]); + + return { + isOpen, + modalContent, + setModalContent, + onOpen, + onClose, + toggle, + }; +}; + +export default useModal; diff --git a/src/main.jsx b/src/main.jsx index 22ec247..6d4a237 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,11 +1,14 @@ +import { CreditProvider } from "@/app/contexts/CreditContext"; import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import AppRouter from "./app/AppRouter"; -import { GlobalStyle } from "./styles/GlobalStyle"; +import { GlobalStyle } from "./styles/globalStyle"; createRoot(document.getElementById("root")).render( - - + + + + ); diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx deleted file mode 100644 index 36e3134..0000000 --- a/src/pages/Home.jsx +++ /dev/null @@ -1,9 +0,0 @@ -function Home() { - return ( - <> -
나는 홈페이지임
- - ); -} - -export default Home; diff --git a/src/pages/HomePage/index.jsx b/src/pages/HomePage/index.jsx new file mode 100644 index 0000000..1f0189a --- /dev/null +++ b/src/pages/HomePage/index.jsx @@ -0,0 +1,151 @@ +import useCreditContext from "@/app/contexts/CreditContext"; +import imgMonthlyBg from "@/assets/imgs/home_monthly_bg.png"; +import imgMonthyPhone from "@/assets/imgs/home_monthly_phone.png"; +import imgMyArtistBg from "@/assets/imgs/home_my_artist_bg.png"; +import imgMyArtistPhone from "@/assets/imgs/home_my_artist_phone.png"; +import imgSponsorBg from "@/assets/imgs/home_sponsor_bg.png"; +import imgSponsorPhone from "@/assets/imgs/home_sponsor_phone.png"; +import imgStartBg from "@/assets/imgs/home_start_bg.png"; +import imgLogo from "@/assets/imgs/logo.svg"; +import HighlightButton from "@/components/common/HighlightButton"; +import { media } from "@/styles/media"; +import { useNavigate } from "react-router-dom"; +import { css } from "styled-components"; +import * as S from "./index.style"; + +const Homepage = () => { + const navigate = useNavigate(); + const [_, actions] = useCreditContext(); + + const goListPage = () => { + navigate("/list"); + }; + + const resetCredit = () => { + actions.resetCredit(); + }; + + return ( + + + + + + + + ); +}; + +const StartSection = ({ onClick }) => { + return ( + + + 팬덤 케이 + + + 내가 좋아하는 아이돌을 +
+ 가장 쉽게 덕질 하는 방법 +
+ 팬덤케이 로고 { + onClick.goListPage(); + }} + /> +
+ 지금 시작하기 + { + onClick.goListPage(); + onClick.resetCredit(); + }} + > + 지금 시작하기 + +
+
+ ); +}; + +const SponsorSection = () => { + return ( + + + + + + 후원하기 + + 좋아하는 아이돌에게 +
+ 쉽게 조공해 보세요 +
+
+ + +
+
+ ); +}; + +const MonthlySection = () => { + return ( + + + + + + 이달의 아티스트 + + 내 아티스트에게 1등의 +
+ 영예를 선물하세요 +
+
+ + +
+
+ ); +}; + +const MyArtistSection = () => { + return ( + + + + + + 나만의 아티스트 + + 좋아하는 아티스트들의 +
+ 소식을 모아보세요 +
+
+ + +
+
+ ); +}; + +export default Homepage; diff --git a/src/pages/HomePage/index.style.js b/src/pages/HomePage/index.style.js new file mode 100644 index 0000000..1299a42 --- /dev/null +++ b/src/pages/HomePage/index.style.js @@ -0,0 +1,253 @@ +import { media } from "@/styles/media"; +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Container = styled.div` + background-color: ${hexToRgba(COLOR_VAR_MAP["--color-bg-dark"])}; + overflow-x: hidden; +`; + +export const Section = styled.section` + display: flex; + height: 812px; + flex-direction: column; + justify-content: center; + align-items: center; + + @media ${media.tablet} { + height: 744px; + } + + @media ${media.desktop} { + height: 1200px; + } +`; + +export const FirstSection = styled(Section)` + height: 812px; + + @media ${media.tablet} { + height: 1200px; + } + + @media ${media.desktop} { + height: 1080px; + } +`; + +export const FirstSectionTitle = styled.h2` + color: ${hexToRgba("#ffffffde")}; + ${TYPO.titleLg} +`; + +export const FirstSectionBox = styled.div` + display: flex; + position: relative; + width: 1080px; + height: 100%; + flex-direction: column; + align-items: center; + justify-content: space-between; + + & > img { + overflow: hidden; + position: absolute; + top: 50%; + width: 100%; + height: 100%; + object-fit: contain; + transform: translateY(-50%) scale(121.6%); + } + + @media ${media.tablet} { + & > img { + transform: translateY(-50%) scale(111.2%); + } + } + + @media ${media.desktop} { + & > img { + overflow: visible; + top: 0; + transform: translateY(-5%); + } + } +`; + +export const Blind = styled.h1` + overflow: hidden; + position: absolute !important; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + border: 0; + + white-space: nowrap; + clip-path: inset(50%); +`; + +export const LogoContainer = styled.div` + display: flex; + z-index: 1; + margin-top: 100px; + + ${TYPO.title20Regular} + flex-direction: column; + align-items: center; + gap: 20px; + + & span { + color: var(--color-primary); + } + + & > img { + width: 236.64px; + height: 45.1px; + } + + @media ${media.tablet} { + gap: 32px; + margin-top: 120px; + + font-weight: 700; + + & > img { + width: 325.34px; + height: 62px; + } + } + + @media ${media.desktop} { + gap: 29px; + margin-top: 140px; + + font-size: 26px; + + & > img { + width: 509px; + height: 97px; + } + } +`; + +export const SectionBox = styled.div` + display: flex; + position: relative; + width: 100%; + height: 100%; + flex-direction: column; + align-items: center; + + @media ${media.desktop} { + width: 1200px; + } +`; + +export const SectionBg = styled.img` + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: contain; +`; + +export const SectionTitleContainer = styled.div` + display: flex; + z-index: 1; + margin-top: 93px; + + color: white; + flex-direction: column; + gap: 8px; + align-items: center; + + @media ${media.tablet} { + margin-top: 84px; + } + + @media ${media.desktop} { + margin-top: 160px; + } +`; + +export const SectionTitle = styled.div` + color: ${hexToRgba(COLOR_VAR_MAP["--color-accent"])}; + font-weight: 500; + font-size: 16px; +`; + +export const SectionInfo = styled.div` + font-weight: 700; + font-size: 20px; + text-align: center; + + @media ${media.desktop} { + font-size: 24px; + } +`; + +export const SectionImg1 = styled.img` + z-index: 1; + width: 240px; + height: 520.25px; + margin-top: 47px; + animation-name: phone-ani; + animation-duration: 1.2s; + animation-iteration-count: infinite; + animation-timing-function: ease-in-out; + + @media ${media.tablet} { + width: 200px; + height: 433.54px; + } + + @media ${media.desktop} { + width: 320px; + height: 693.66px; + margin-top: 60px; + } + + @keyframes phone-ani { + 0% { + transform: translateY(0); + } + + 50% { + transform: translateY(10px); + } + + 100% { + transform: translateY(0); + } + } +`; + +export const VerticalLine = styled.div` + position: absolute; + top: 1029px; + left: 50%; + width: 117px; + height: 2133px; + background: linear-gradient( + to bottom, + ${hexToRgba("#030615")}, + ${hexToRgba("#051d31")}, + ${hexToRgba("#051e32")}, + ${hexToRgba("#051c30")}, + ${hexToRgba("#030b1c")} + ); + transform: translateX(-50%); + + @media ${media.tablet} { + top: 1394px; + height: 1928px; + } + + @media ${media.desktop} { + top: 1393px; + width: 187px; + height: 3091px; + } +`; diff --git a/src/pages/List.jsx b/src/pages/List.jsx deleted file mode 100644 index 67afee6..0000000 --- a/src/pages/List.jsx +++ /dev/null @@ -1,9 +0,0 @@ -function List() { - return ( - <> -
나는 목록페이지임
- - ); -} - -export default List; diff --git a/src/pages/ListPage/components/chart/ChartSection.jsx b/src/pages/ListPage/components/chart/ChartSection.jsx new file mode 100644 index 0000000..a0c279c --- /dev/null +++ b/src/pages/ListPage/components/chart/ChartSection.jsx @@ -0,0 +1,162 @@ +import client from "@/api/client"; +import HighlightButton from "@/components/common/HighlightButton"; +import useModal from "@/hooks/useModal"; +import { useEffect, useState } from "react"; +import { useErrorBoundary } from "react-error-boundary"; +import { css } from "styled-components"; +import * as S from "./ChartSection.styles"; +import ListItem from "./ListItem"; +import VoteModal from "./VoteModal"; + +const BREAKPOINT = 744; + +const ChartSection = () => { + const { showBoundary } = useErrorBoundary(); + const getInitWidth = () => (typeof window !== "undefined" ? window.innerWidth : BREAKPOINT); + + const [gender, setGender] = useState("female"); + const { isOpen, onOpen, onClose } = useModal(); + + const [windowWidth, setWindowWidth] = useState(getInitWidth()); + const [visibleCount, setVisibleCount] = useState(getInitWidth() >= BREAKPOINT ? 10 : 5); + + const [list, setList] = useState([]); + + useEffect(() => { + const loadIdols = async () => { + try { + const res = await client.get("/idols", { params: { pageSize: 100 } }); + const idols = res.data.list; + + const filtered = idols.filter((i) => i.gender === gender); + + const sorted = filtered + .sort((a, b) => b.totalVotes - a.totalVotes) + .map((i, idx) => ({ + id: i.id, + name: i.name, + img: i.profilePicture, + votes: i.totalVotes, + rank: idx + 1, + })); + + setList(sorted); + } catch (e) { + showBoundary(e); + } + }; + loadIdols(); + }, [gender, showBoundary]); + + useEffect(() => { + const onResize = () => setWindowWidth(window.innerWidth); + window.addEventListener("resize", onResize); + return () => window.removeEventListener("resize", onResize); + }, []); + + useEffect(() => { + setVisibleCount(windowWidth >= BREAKPOINT ? 10 : 5); + }, [windowWidth]); + + const data = list; + const shown = data.slice(0, visibleCount); + + const colA = shown.filter((_, i) => i % 2 === 0); + const colB = shown.filter((_, i) => i % 2 === 1); + + const isSingleCol = windowWidth < BREAKPOINT; + + const handleSelectGender = (g) => { + if (g === gender) return; + setGender(g); + setVisibleCount(windowWidth >= BREAKPOINT ? 10 : 5); + }; + + const handleMore = () => { + setVisibleCount((v) => Math.min(v + 5, data.length)); + }; + + let gridContent; + if (isSingleCol) { + gridContent = ( + + {shown.map((c) => ( + + ))} + + ); + } else { + gridContent = ( + <> + + {colA.map((c) => ( + + ))} + + + {colB.map((c) => ( + + ))} + + + ); + } + + return ( + + + 이달의 차트 + + + + 차트 투표하기 + + + + + + + handleSelectGender("female")} + > + 이달의 여자 아이돌 + + handleSelectGender("male")} + > + 이달의 남자 아이돌 + + + + {gridContent} + + + + = data.length}> + 더 보기 + + + + {isOpen && } + + ); +}; + +export default ChartSection; diff --git a/src/pages/ListPage/components/chart/ChartSection.styles.js b/src/pages/ListPage/components/chart/ChartSection.styles.js new file mode 100644 index 0000000..5f952ae --- /dev/null +++ b/src/pages/ListPage/components/chart/ChartSection.styles.js @@ -0,0 +1,132 @@ +import frame from "@/assets/Chart.png"; +import { media } from "@/styles/media"; +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Wrap = styled.section` + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + padding: 0 24px 80px; + + @media (${media.desktop}) { + padding-bottom: 80px; + } +`; + +export const ChartHeader = styled.div` + width: 100%; + max-width: 1200px; + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + gap: 12px; + margin-bottom: 16px; + + @media (${media.tablet}) { + margin-bottom: 24px; + } +`; + +export const H2 = styled.h2` + margin: 0; + font-size: 20px; + + @media (${media.tablet}) { + ${TYPO.titleMd} + } +`; + +export const RightArea = styled.div` + display: flex; + align-items: center; + gap: 10px; +`; + +export const ChartImg = styled.div` + width: 24px; + height: 24px; + background-repeat: no-repeat; + background-size: cover; + background-image: url(${frame}); +`; + +export const ChartVote = styled.p` + margin-left: 4px; + ${TYPO.caption14Bold}; +`; + +export const Board = styled.div` + width: 100%; + max-width: 1200px; +`; + +export const BoardHead = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + height: 48px; +`; + +export const HeadTab = styled.button` + appearance: none; + ${TYPO.body14Medium}; + cursor: pointer; + background: ${({ $active }) => ($active ? hexToRgba("#1b2029") : "")}; + color: ${({ $active }) => + $active ? `${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}` : hexToRgba("#828282")}; + border-bottom: ${({ $active }) => + $active ? `2px solid ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}` : "none"}; + transition: + background 0.2s ease, + color 0.2s ease; + + &:focus-visible { + outline: 2px solid ${hexToRgba("#3b82f6")}; + outline-offset: 0; + } +`; + +export const Grid = styled.div` + display: grid; + width: 100%; + max-width: 1200px; + margin: 0 auto; + + grid-template-columns: 1fr; + gap: 0; + + @media (${media.tablet}) { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 24px; + } +`; + +export const Col = styled.div` + padding: 0; + font-size: 14px; + + @media (${media.tablet}) { + padding: 24px 0 0; + font-size: 16px; + } +`; + +export const MoreArea = styled.div` + width: 100%; + max-width: 524px; + margin: 25px auto 0; +`; + +export const MoreBtn = styled.button` + width: 100%; + height: 46px; + background: ${hexToRgba("#222734")}; + color: ${hexToRgba("#fff")}; + border: 1px solid ${hexToRgba("#F1EEF9CC")}; + border-radius: 3px; + cursor: pointer; + ${TYPO.caption14Bold} + opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; +`; diff --git a/src/pages/ListPage/components/chart/CreditLimitModal.jsx b/src/pages/ListPage/components/chart/CreditLimitModal.jsx new file mode 100644 index 0000000..15b4941 --- /dev/null +++ b/src/pages/ListPage/components/chart/CreditLimitModal.jsx @@ -0,0 +1,30 @@ +import HighlightButton from "@/components/common/HighlightButton"; +import { css } from "styled-components"; +import * as S from "./CreditLimitModal.style"; + +const CreditLimitModal = ({ onClose }) => { + return ( + + + + + + 앗! 투표하기 위한 크레딧이 부족해요. + + + 확인 + + + + ); +}; + +export default CreditLimitModal; diff --git a/src/pages/ListPage/components/chart/CreditLimitModal.style.js b/src/pages/ListPage/components/chart/CreditLimitModal.style.js new file mode 100644 index 0000000..e434a2b --- /dev/null +++ b/src/pages/ListPage/components/chart/CreditLimitModal.style.js @@ -0,0 +1,61 @@ +import closePng from "@/assets/btn_delete_24px.png"; +import credit_icon from "@/assets/credit_113px.png"; +import { TYPO } from "@/styles/typography"; +import { hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Overlay = styled.div` + display: flex; + position: fixed; + z-index: 999; + inset: 0; + background: ${hexToRgba("#000000B2")}; + justify-content: center; + align-items: center; +`; + +export const ModalBox = styled.div` + display: flex; + position: relative; + width: 339px; + height: 330px; + background: ${hexToRgba("#181D26FF")}; + border-radius: 12px; + flex-direction: column; + align-items: center; +`; + +export const Icon = styled.div` + width: 133px; + height: 113px; + background-image: url(${credit_icon}); + background-repeat: no-repeat; + background-size: cover; + background-position: center; + margin: 56px 24px 0; +`; + +export const Message = styled.p` + margin-top: 31px; + + color: var(--color-white-100); + ${TYPO.body16Medium} + + span { + color: ${hexToRgba("#f77")}; + font-weight: 600; + } +`; + +export const CloseBtn = styled.button` + position: absolute; + top: 24px; + right: 16px; + width: 24px; + height: 24px; + border: none; + background-image: url(${closePng}); + background-repeat: no-repeat; + background-size: cover; + cursor: pointer; +`; diff --git a/src/pages/ListPage/components/chart/ListItem.jsx b/src/pages/ListPage/components/chart/ListItem.jsx new file mode 100644 index 0000000..ca9cfad --- /dev/null +++ b/src/pages/ListPage/components/chart/ListItem.jsx @@ -0,0 +1,26 @@ +import IdolCircleImage from "@/components/common/IdolCircleImage"; +import { memo } from "react"; +import * as S from "./ListItem.style"; + +const ListItem = ({ id, img, rank, name, votes, selected, onSelect, variant = "vote" }) => { + const commaNum = (num) => Number(num).toLocaleString(); + + return ( + onSelect?.(id) : undefined} + > + + {rank} + + {name} + {commaNum(votes)}표 + + + {variant === "vote" && } + + ); +}; + +export default memo(ListItem); diff --git a/src/pages/ListPage/components/chart/ListItem.style.js b/src/pages/ListPage/components/chart/ListItem.style.js new file mode 100644 index 0000000..7fd842f --- /dev/null +++ b/src/pages/ListPage/components/chart/ListItem.style.js @@ -0,0 +1,73 @@ +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Item = styled.div` + display: flex; + align-items: center; + gap: 10px; + border-bottom: 1px solid ${hexToRgba("#E1E1E11A")}; + cursor: ${({ $variant }) => ($variant === "vote" ? "pointer" : "default")}; + transition: ${({ $variant }) => ($variant === "vote" ? "background 0.2s ease" : "none")}; + padding: 8px 0; + + &:last-child { + border: none; + } +`; + +export const Rank = styled.span` + width: 18px; + + color: ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}; + ${TYPO.body14Medium} + text-align: right; + opacity: 0.9; +`; + +export const TextGroup = styled.div` + display: flex; + align-items: center; + gap: 8px; + flex: 1; + min-width: 0; +`; + +export const Name = styled.p` + overflow: hidden; + margin: 0; + + font-weight: 500; + white-space: nowrap; + text-overflow: ellipsis; +`; + +export const ListVotes = styled.p` + margin: 0 0 0 auto; + + color: ${hexToRgba("#aaa")}; + ${TYPO.body14Medium} +`; + +export const RadioVisual = styled.span` + display: ${({ $variant }) => ($variant === "chart" ? "none" : "flex")}; + + width: 18px; + height: 18px; + border-radius: 50%; + border: 2px solid + ${({ $selected }) => + $selected ? ` ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}` : hexToRgba("#666")}; + align-items: center; + justify-content: center; + + &::after { + content: ""; + width: 8px; + height: 8px; + border-radius: 50%; + background: ${({ $selected }) => + $selected ? ` ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}` : hexToRgba("#666")}; + transition: background 0.2s ease; + } +`; diff --git a/src/pages/ListPage/components/chart/VoteModal.jsx b/src/pages/ListPage/components/chart/VoteModal.jsx new file mode 100644 index 0000000..9d05196 --- /dev/null +++ b/src/pages/ListPage/components/chart/VoteModal.jsx @@ -0,0 +1,122 @@ +import client from "@/api/client"; +import HighlightButton from "@/components/common/HighlightButton"; +import { creditStorage } from "@/storage/credit.storage"; +import { media } from "@/styles/media"; +import { useEffect, useState } from "react"; +import { css } from "styled-components"; +import CreditLimitModal from "./CreditLimitModal"; +import ListItem from "./ListItem"; +import * as S from "./VoteModal.style"; + +const VoteModal = ({ onClose, initialGender = "female" }) => { + const [gender] = useState(initialGender); + const [list, setList] = useState([]); + const [selectedId, setSelectedId] = useState(null); + + const [isCreditModalOpen, setIsCreditModalOpen] = useState(false); + + useEffect(() => { + const fetchIdols = async () => { + const res = await client.get("/idols"); + const idols = res.data.list; + + const filtered = idols.filter((i) => i.gender === gender); + + const sorted = filtered + .sort((a, b) => b.totalVotes - a.totalVotes) + .map((i, idx) => ({ + id: i.id, + name: i.name, + img: i.profilePicture, + votes: i.totalVotes, + rank: idx + 1, + })); + + setList(sorted); + }; + + fetchIdols(); + }, [gender]); + + const submit = async () => { + if (!selectedId) return; + + const currentCredit = creditStorage.get() || 0; + + if (currentCredit < 1000) { + setIsCreditModalOpen(true); + return; + } + + const res = await client.post("/votes", { idolId: selectedId }); + const updated = res.data.idol; + + creditStorage.set(currentCredit - 1000); + + const newList = list + .map((i) => (i.id === updated.id ? { ...i, votes: updated.totalVotes } : i)) + .sort((a, b) => b.votes - a.votes) + .map((i, idx) => ({ ...i, rank: idx + 1 })); + + setList(newList); + setSelectedId(null); + }; + + return ( + <> + + e.stopPropagation()}> + + + {gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"} + + + + + {gender === "female" ? "이달의 여자 아이돌" : "이달의 남자 아이돌"} + + + + + {list.map((c) => ( + + ))} + + + + + 투표하기 + + + 투표하는 데 1000 크레딧이 소모됩니다. + + + + + + {isCreditModalOpen && setIsCreditModalOpen(false)} />} + + ); +}; + +export default VoteModal; diff --git a/src/pages/ListPage/components/chart/VoteModal.style.js b/src/pages/ListPage/components/chart/VoteModal.style.js new file mode 100644 index 0000000..1642772 --- /dev/null +++ b/src/pages/ListPage/components/chart/VoteModal.style.js @@ -0,0 +1,157 @@ +import closePng from "@/assets/btn_delete_24px.png"; +import backPng from "@/assets/icj_arrow_left2.png"; +import { media } from "@/styles/media"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Container = styled.div` + display: block; + z-index: 999; + background: ${hexToRgba("#000000B2")}; + inset: 0; + + @media (${media.tablet}) { + position: fixed; + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + } +`; + +export const Modal = styled.div` + position: fixed; + inset: 0; + width: 100vw; + height: 100vh; + max-width: none; + border-radius: 0; + padding: 16px; + background: var(--color-bg-base); + + @media (${media.tablet}) { + position: static; + display: flex; + overflow: hidden; + width: 100%; + height: 693px; + margin: 0 auto; + padding: 24px; + + color: var(--color-white-100); + max-width: 524px; + flex-direction: column; + border-radius: 16px; + } +`; + +export const Header = styled.div` + display: none; + + @media (${media.tablet}) { + display: flex; + width: 100%; + height: 24px; + padding-bottom: 34px; + justify-content: space-between; + } +`; + +export const MobileHeader = styled.div` + display: flex; + position: relative; + width: 100%; + height: 44px; + justify-content: space-between; + align-items: center; + + @media (${media.tablet}) { + display: none; + } +`; + +export const Rbox = styled.div` + display: block; + width: 24px; + height: 24px; + + @media (${media.tablet}) { + display: none; + } +`; + +export const BackBtn = styled.button` + display: block; + width: 24px; + height: 24px; + background: url(${backPng}) no-repeat center / contain; + border: 0; + cursor: pointer; + + @media (${media.tablet}) { + display: none; + } +`; + +export const Title = styled.h2` + margin: 0; + font-size: 14px; + + @media (${media.tablet}) { + font-weight: 500; + font-size: 18px; + } +`; + +export const CloseBtn = styled.button` + width: 24px; + height: 24px; + background: transparent url(${closePng}) no-repeat center / contain; + border: 0; + cursor: pointer; +`; + +export const List = styled.div` + height: 100%; + + @media (${media.tablet}) { + flex: 1; + min-height: 0; + overflow-y: auto; + + /* 스크롤바 숨김(필요 시 제거 가능) */ + scrollbar-width: none; + -ms-overflow-style: none; + + &:-webkit-scrollbar { + display: none; + } + } +`; + +export const Vote = styled.div` + margin: 0 auto; + + position: fixed; + bottom: 0; + z-index: 10; + height: 106px; + background: transparent; + width: 100%; + + @media (${media.tablet}) { + position: static; + text-align: center; + } +`; + +export const VoteNotice = styled.p` + margin: 0; + + font-size: 12px; +`; + +export const Credit = styled.span` + color: ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}; +`; diff --git a/src/pages/ListPage/components/credit/MyCredit.jsx b/src/pages/ListPage/components/credit/MyCredit.jsx new file mode 100644 index 0000000..3d346f8 --- /dev/null +++ b/src/pages/ListPage/components/credit/MyCredit.jsx @@ -0,0 +1,35 @@ +import useCreditContext from "@/app/contexts/CreditContext"; +import { useState } from "react"; +import CreditSvg from "../../../../assets/svg/CreditSvg"; +import Modal from "../../../../components/common/Modal"; +import * as S from "./MyCredit.style"; +import RechargeModalContent from "./RechargeModalContent"; + +const MyCredit = () => { + const [showRechargeModal, setShowRechargeModal] = useState(false); + const [credit] = useCreditContext(); + + return ( + <> + + + 내 크레딧 + + +
{credit.toLocaleString()}
+
+
+ setShowRechargeModal(true)}>충전하기 +
+ setShowRechargeModal(false)} + > + setShowRechargeModal(false)} /> + + + ); +}; + +export default MyCredit; diff --git a/src/pages/ListPage/components/credit/MyCredit.style.js b/src/pages/ListPage/components/credit/MyCredit.style.js new file mode 100644 index 0000000..5db38ea --- /dev/null +++ b/src/pages/ListPage/components/credit/MyCredit.style.js @@ -0,0 +1,64 @@ +import { media } from "@/styles/media"; +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + width: auto; + height: 131px; + margin: 16px 24px 40px; + padding: 0 20px; + border: 1px solid ${hexToRgba("#f1eef9cc")}; + border-radius: 8px; + justify-content: space-between; + align-items: center; + + @media ${media.tablet} { + margin: 0 24px 64px; + padding: 0 64px; + } + + @media ${media.desktop} { + width: 1200px; + margin: 50px auto; + padding: 0 78px; + } +`; + +export const CreditContainer = styled.div` + display: flex; + flex-direction: column; + gap: 14px; +`; + +export const CreditTitle = styled.div` + color: ${hexToRgba("#fff9")}; + ${TYPO.caption12Regular} + + @media ${media.tablet} { + font-size: 16px; + } +`; + +export const CreditPoint = styled.div` + display: flex; + + ${TYPO.title20Bold} + align-items: center; + + @media ${media.tablet} { + font-size: 24px; + } +`; + +export const RechargeButton = styled.button` + color: ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}; + ${TYPO.caption14Bold}; + line-height: 26px; + cursor: pointer; + + @media ${media.tablet} { + font-size: 16px; + } +`; diff --git a/src/pages/ListPage/components/credit/RechargeModalContent.jsx b/src/pages/ListPage/components/credit/RechargeModalContent.jsx new file mode 100644 index 0000000..b892ed0 --- /dev/null +++ b/src/pages/ListPage/components/credit/RechargeModalContent.jsx @@ -0,0 +1,58 @@ +import useCreditContext from "@/app/contexts/CreditContext"; +import HighlightButton from "@/components/common/HighlightButton"; +import { useState } from "react"; +import { css } from "styled-components"; +import CreditSvg from "../../../../assets/svg/CreditSvg"; +import CreditWhite from "../../../../assets/svg/CreditWhiteSvg"; +import RadioOffSvg from "../../../../assets/svg/RadioOffSvg"; +import RadioOnSvg from "../../../../assets/svg/RadioOnSvg"; +import * as S from "./RechargeModalContent.style"; + +const CREDITS = [100, 500, 1000]; + +const RechargeModalContent = ({ onClose }) => { + const [selectedCredit, setSelectedCredit] = useState(CREDITS[0]); + + const actions = useCreditContext()[1]; + + const onRechargeClick = () => { + actions.addCredit(selectedCredit); + onClose(); + }; + + return ( + + + {CREDITS.map((it) => ( + setSelectedCredit(it)} + > + +
{it}
+ {it == selectedCredit ? : } +
+ ))} +
+ + +
충전하기
+
+
+ ); +}; + +export default RechargeModalContent; diff --git a/src/pages/ListPage/components/credit/RechargeModalContent.style.js b/src/pages/ListPage/components/credit/RechargeModalContent.style.js new file mode 100644 index 0000000..f885b59 --- /dev/null +++ b/src/pages/ListPage/components/credit/RechargeModalContent.style.js @@ -0,0 +1,34 @@ +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Container = styled.div` + display: flex; + flex-direction: column; + margin-top: 24px; +`; + +export const CreditList = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +export const Credit = styled.div` + display: flex; + padding: 18px 20px; + border: 1px solid + ${({ selected }) => + selected + ? `${hexToRgba(COLOR_VAR_MAP["--color-primary"])};` + : `${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}`}; + align-items: center; + border-radius: 8px; + cursor: pointer; + + & > div { + color: var(--color-white-100); + ${TYPO.title20Bold} + flex: 1; + } +`; diff --git a/src/pages/ListPage/components/donation/DonationModal.jsx b/src/pages/ListPage/components/donation/DonationModal.jsx new file mode 100644 index 0000000..a22c2c1 --- /dev/null +++ b/src/pages/ListPage/components/donation/DonationModal.jsx @@ -0,0 +1,70 @@ +import { contributeDonation } from "@/api/donationsClinet"; +import useCreditContext from "@/app/contexts/CreditContext"; +import { isPositiveInteger } from "@/utils/number"; +import { useState } from "react"; +import DonationModalUi from "./DonationModalUi"; + +const DonationModal = ({ onSuccess, isOpen, onClose, content }) => { + const [donationCredit, setDonationCredit] = useState(""); + const [isLoading, setIsLoading] = useState(false); // 데이터 요청 로딩 상태 + const [, { isEnoughCredit, subtractCredit }] = useCreditContext(); + + const isNotEnough = !isEnoughCredit(donationCredit); + + const handleSetCredit = (e) => { + const inputValue = e.target.value; + + // 숫자 or 빈 문자열만 허용 + if (inputValue === "" || isPositiveInteger(inputValue)) { + setDonationCredit(inputValue); + } + }; + + const resetState = () => { + setDonationCredit(""); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); // 새로고침 방지 + const amount = Number(donationCredit); + try { + setIsLoading(true); // 로딩 시작 + // 서버에 기부 요청 + await contributeDonation(content.id, { amount }); + subtractCredit(amount); + onClose(); + resetState(); + onSuccess({ + donationId: content.id, + amount, + }); + } catch (error) { + console.error("후원 실패:", error); + } finally { + setIsLoading(false); // 로딩 종료 + } + }; + + const handleClose = () => { + resetState(); + onClose(); + }; + + // 내용이 없으면 리턴 + if (!content) return null; + + return ( + + ); +}; + +export default DonationModal; diff --git a/src/pages/ListPage/components/donation/DonationModalUi.jsx b/src/pages/ListPage/components/donation/DonationModalUi.jsx new file mode 100644 index 0000000..1a9cb40 --- /dev/null +++ b/src/pages/ListPage/components/donation/DonationModalUi.jsx @@ -0,0 +1,61 @@ +import creditImg from "@/assets/imgs/credit.png"; +import HighlightButton from "@/components/common/HighlightButton"; +import Modal from "@/components/common/Modal"; +import { css } from "styled-components"; +import * as S from "./DonationModalUi.style"; + +const DonationModalUi = ({ + isOpen, + onClose, + content, + donationCredit, + isNotEnough, + isLoading, + onChangeCredit, + onSubmit, +}) => { + return ( + + + {content.idol.name} + + {content.subtitle} + {content.title} + + +
+ + + 크레딧 + + {isNotEnough && ( + 갖고 있는 크레딧보다 더 많이 후원할 수 없어요 + )} + {/* 🔥 로딩 메시지 */} + {isLoading && ( + 후원 요청 중입니다... + )} +
+ + 후원하기 + +
+
+
+ ); +}; + +export default DonationModalUi; diff --git a/src/pages/ListPage/components/donation/DonationModalUi.style.js b/src/pages/ListPage/components/donation/DonationModalUi.style.js new file mode 100644 index 0000000..c1ba28d --- /dev/null +++ b/src/pages/ListPage/components/donation/DonationModalUi.style.js @@ -0,0 +1,78 @@ +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const ChildrenWrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 295px; + margin-top: 24px; + + img { + width: 158px; + height: 206px; + object-fit: cover; + } +`; + +export const TitleWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + width: 158px; + margin: 10px 2px 24px; +`; + +export const SubTitle = styled.h4` + color: ${hexToRgba("#666")}; + ${TYPO.body14Medium} +`; + +export const Title = styled.h4` + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}; + ${TYPO.body14Medium} +`; + +export const FormContainer = styled.form` + display: flex; + flex-direction: column; + gap: 24px; +`; + +export const InputWrapper = styled.div` + position: relative; + border: solid 1px ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; + border-radius: 8px; + padding: 16px; + padding-right: 48px; + + input { + width: 231px; + height: 26px; + padding: 0; + + ${TYPO.title20Bold} + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; + } + + img { + position: absolute; + top: 16px; + right: 16px; + width: 24px; + height: 24px; + } + + ${({ $isNotEnough }) => $isNotEnough && { borderColor: hexToRgba("#FF3B3B") }} +`; + +export const MessageBox = styled.div` + margin-top: 8px; + + ${TYPO.caption12SemiBold} + color: ${hexToRgba("#ff2626")}; + + ${({ $color }) => $color && { color: $color }} +`; diff --git a/src/pages/ListPage/components/donation/DonationSection.jsx b/src/pages/ListPage/components/donation/DonationSection.jsx new file mode 100644 index 0000000..02f81b5 --- /dev/null +++ b/src/pages/ListPage/components/donation/DonationSection.jsx @@ -0,0 +1,82 @@ +import { getDonationList } from "@/api/donationsClinet"; +import useModal from "@/hooks/useModal"; +import { useCallback, useEffect, useState } from "react"; +import { useErrorBoundary } from "react-error-boundary"; +import DonationModal from "./DonationModal"; +import * as S from "./DonationSection.style"; +import DonationSlider from "./DonationSlider"; + +const PAGE_SIZE = 5; + +const DonationSection = () => { + const { showBoundary } = useErrorBoundary(); + const [list, setList] = useState([]); + const [isLoadingMore, setIsLoadingMore] = useState(false); + const [cursor, setCursor] = useState(undefined); + const { isOpen, modalContent, setModalContent, onOpen, onClose } = useModal(); + + // 최초 로딩 + useEffect(() => { + const fetchInitial = async () => { + try { + const res = await getDonationList({ pageSize: PAGE_SIZE }); + setList(res.list); + setCursor(res.nextCursor); + } catch (e) { + showBoundary(e); + } + }; + fetchInitial(); + }, [showBoundary]); + + const hasMore = cursor != null; + // 마지막에서 다음 페이지 요청 + const handleReachEnd = useCallback(async () => { + if (!hasMore || isLoadingMore) return; + + setIsLoadingMore(true); + const res = await getDonationList({ + pageSize: PAGE_SIZE, + cursor, + }); + + setList((prev) => [...prev, ...res.list]); + setCursor(res.nextCursor); + setIsLoadingMore(false); + }, [cursor, hasMore, isLoadingMore, setCursor]); + + // 후원 성공시 리스트 갱신 + const handleDonationSuccess = ({ donationId, amount }) => { + setList((prevList) => + prevList.map((item) => + item.id === donationId + ? { + ...item, + receivedDonations: item.receivedDonations + amount, + } + : item + ) + ); + }; + return ( + <> + + 후원을 기다리는 조공 + + + + + ); +}; + +export default DonationSection; diff --git a/src/pages/ListPage/components/donation/DonationSection.style.js b/src/pages/ListPage/components/donation/DonationSection.style.js new file mode 100644 index 0000000..119c7e1 --- /dev/null +++ b/src/pages/ListPage/components/donation/DonationSection.style.js @@ -0,0 +1,33 @@ +import { media } from "@/styles/media"; +import { TYPO } from "@/styles/typography"; +import styled from "styled-components"; + +export const Contaier = styled.section` + padding-left: 24px; + margin-bottom: 40px; + + @media ${media.tablet} { + margin-bottom: 60px; + } + + @media ${media.desktop} { + width: 1200px; + margin: 0 auto; + padding-left: 0; + margin-bottom: 80px; + } +`; + +export const DonationTitle = styled.h2` + margin-bottom: 16px; + + ${TYPO.title18SemiBold} + + @media ${media.tablet} { + font-size: 2rem; + } + + @media ${media.desktop} { + font-size: 2.4rem; + } +`; diff --git a/src/pages/ListPage/components/donation/DonationSlider.jsx b/src/pages/ListPage/components/donation/DonationSlider.jsx new file mode 100644 index 0000000..dfd4885 --- /dev/null +++ b/src/pages/ListPage/components/donation/DonationSlider.jsx @@ -0,0 +1,58 @@ +import ArrowButton from "@/components/common/ArrowButton"; +import useDraggableSlider from "@/hooks/useDraggableSlider"; +import useIntersectionObserver from "@/hooks/useIntersectionObserver"; +import * as S from "./DonationSlider.style"; +import FundingCard from "./FundingCard"; +import FundingCardSkeletonComponent from "./FundingCardSkeleton"; + +const DonationSlider = ({ list, onClick, handleReachEnd, cursor }) => { + const drag = useDraggableSlider(cursor); + + // sentinelRef가 보이면 함수 호출 + const sentinelRef = useIntersectionObserver({ + rootRef: drag.viewportRef, + enabled: drag.hasMore, + onIntersect: () => { + handleReachEnd(); + }, + threshold: 0.5, + }); + + return ( + + + + + {list.map((item) => ( + + ))} + {cursor !== null && ( +
+ +
+ )} +
+
+ +
+ ); +}; + +export default DonationSlider; diff --git a/src/pages/ListPage/components/donation/DonationSlider.style.js b/src/pages/ListPage/components/donation/DonationSlider.style.js new file mode 100644 index 0000000..2aaf04a --- /dev/null +++ b/src/pages/ListPage/components/donation/DonationSlider.style.js @@ -0,0 +1,37 @@ +import { media } from "@/styles/media"; +import styled from "styled-components"; + +export const SlideWrapper = styled.div` + display: flex; + position: relative; + align-items: center; + + @media ${media.desktop} { + gap: 40px; + width: 1200px; + } + + @media ${media.desktopSlider} { + gap: 40px; + } +`; + +export const SlideTrack = styled.div` + display: flex; + gap: 8px; + will-change: transform; + + @media ${media.tablet} { + gap: 16px; + } + + @media ${media.desktop} { + gap: 24px; + } +`; + +export const FundingCardWrapper = styled.div` + display: flex; + overflow: hidden; + width: 100%; +`; diff --git a/src/pages/ListPage/components/donation/FundingCard.jsx b/src/pages/ListPage/components/donation/FundingCard.jsx new file mode 100644 index 0000000..695b8c3 --- /dev/null +++ b/src/pages/ListPage/components/donation/FundingCard.jsx @@ -0,0 +1,99 @@ +import creditImg from "@/assets/imgs/credit.png"; +import HighlightButton from "@/components/common/HighlightButton"; +import { media } from "@/styles/media"; +import { getRemainingDays } from "@/utils/date"; +import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; +import { css } from "styled-components"; +import * as S from "./FundingCard.style"; + +const CustomizedTooltip = ({ targetDonation, receivedDonations }) => { + return ( + + {receivedDonations} / {targetDonation} + + ); +}; + +const FundingCard = ({ item, onClick }) => { + const deadline = getRemainingDays(item.deadline); + + return ( + + + {item.idol.name} + { + onClick.onOpen(); + onClick.setModalContent(item); + }} + > + 후원하기 + + + + {item.subtitle} + {item.title} + + + + 크레딧 + {item.receivedDonations.toLocaleString()} + +
{deadline ? `${deadline}일 남음` : "기한 만료"}
+
+ + + + + } + position={{ y: -30 }} + cursor={false} + /> + + + + + + +
+ ); +}; + +export default FundingCard; diff --git a/src/pages/ListPage/components/donation/FundingCard.style.js b/src/pages/ListPage/components/donation/FundingCard.style.js new file mode 100644 index 0000000..efd1266 --- /dev/null +++ b/src/pages/ListPage/components/donation/FundingCard.style.js @@ -0,0 +1,115 @@ +import { media } from "@/styles/media"; +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const FundingCard = styled.div` + display: flex; + flex-direction: column; +`; + +export const ImgWrapper = styled.div` + overflow: hidden; + position: relative; + margin-bottom: 8px; + border-radius: 8px; + + &::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient( + to bottom, + ${hexToRgba("#00000000")} 85%, + ${hexToRgba("#000000FF")} 100% + ); + pointer-events: none; /* 버튼 클릭 방해 안 하도록 */ + } + + img { + width: 158px; + height: 206px; + object-fit: cover; + } + + @media ${media.tablet} { + img { + width: 282px; + height: 294px; + } + + margin-bottom: 12px; + } +`; + +export const TitleWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 20px; + + @media ${media.tablet} { + margin-bottom: 24px; + gap: 6px; + } +`; + +export const SubTitle = styled.h4` + color: ${hexToRgba("#666")}; + ${TYPO.caption12Regular}; + + @media ${media.tablet} { + font-size: 1.6rem; + } +`; + +export const Title = styled.h4` + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}; + ${TYPO.body14Medium}; + + @media ${media.tablet} { + font-size: 1.8rem; + } +`; + +export const ToolTipContainer = styled.div` + padding: 4px 12px; + border-radius: 4px; + + background-color: ${hexToRgba(COLOR_VAR_MAP["--color-bg-base"])}; + + font-size: 1.4rem; +`; + +export const DirectionWrapper = styled.div` + display: flex; + margin-bottom: 8px; + + ${TYPO.caption12Regular}; + justify-content: space-between; +`; + +export const ChartWrapper = styled.div` + width: 158px; + + .recharts-wrapper *:focus:not(:focus-visible) { + outline: none; + } + + @media ${media.tablet} { + width: 282px; + } +`; + +export const ReceivedDonations = styled.div` + display: flex; + align-items: center; + gap: 2px; + + color: var(--color-primary); + + img { + width: 17px; + height: 21px; + } +`; diff --git a/src/pages/ListPage/components/donation/FundingCardSkeleton.jsx b/src/pages/ListPage/components/donation/FundingCardSkeleton.jsx new file mode 100644 index 0000000..fe59306 --- /dev/null +++ b/src/pages/ListPage/components/donation/FundingCardSkeleton.jsx @@ -0,0 +1,20 @@ +import * as S from "./FundingCardSkeleton.style"; + +const FundingCardSkeletonComponent = () => { + return ( + + + + + + + + + + + + + ); +}; + +export default FundingCardSkeletonComponent; diff --git a/src/pages/ListPage/components/donation/FundingCardSkeleton.style.js b/src/pages/ListPage/components/donation/FundingCardSkeleton.style.js new file mode 100644 index 0000000..64232b0 --- /dev/null +++ b/src/pages/ListPage/components/donation/FundingCardSkeleton.style.js @@ -0,0 +1,102 @@ +import { media } from "@/styles/media"; +import { hexToRgba } from "@/utils/color"; +import styled, { keyframes } from "styled-components"; + +const shimmer = keyframes` + 0% { + background-position: -200px 0; + } + 100% { + background-position: 200px 0; + } +`; + +const SkeletonBase = styled.div` + background: linear-gradient( + 90deg, + ${hexToRgba("#2a2a2a")} 0, + ${hexToRgba("#3a3a3a")} 40px, + ${hexToRgba("#2a2a2a")} 80px + ); + background-size: 200px 100%; + + animation: ${shimmer} 1.4s ease-in-out infinite; + border-radius: 4px; +`; + +export const FundingCardSkeleton = styled.div` + display: flex; + flex-direction: column; +`; + +export const ImgSkeleton = styled(SkeletonBase)` + overflow: hidden; + width: 158px; + height: 206px; + margin-bottom: 8px; + border-radius: 8px; + + @media ${media.tablet} { + width: 282px; + height: 294px; + margin-bottom: 12px; + } +`; + +export const TitleWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 20px; + + @media ${media.tablet} { + margin-bottom: 24px; + gap: 6px; + } +`; + +export const SubTitleSkeleton = styled(SkeletonBase)` + width: 80px; + height: 14px; + + @media ${media.tablet} { + width: 110px; + height: 16px; + } +`; + +export const TitleSkeleton = styled(SkeletonBase)` + width: 140px; + height: 18px; + + @media ${media.tablet} { + width: 180px; + height: 20px; + } +`; + +export const DirectionWrapper = styled.div` + display: flex; + margin-bottom: 8px; + + font-weight: 400; + font-size: 1.2rem; + line-height: 1.8rem; + justify-content: space-between; +`; + +export const ToolTipSkeleton = styled(SkeletonBase)` + width: 100px; + height: 20px; +`; + +export const ReceivedDonationsBarSkeleton = styled(SkeletonBase)` + width: 60px; + height: 14px; +`; + +export const ChartBarSkeleton = styled(SkeletonBase)` + width: 100%; + height: 4px; + border-radius: 4px; +`; diff --git a/src/pages/ListPage/index.jsx b/src/pages/ListPage/index.jsx new file mode 100644 index 0000000..4c27a78 --- /dev/null +++ b/src/pages/ListPage/index.jsx @@ -0,0 +1,20 @@ +import SectionErrorBoundary from "@/components/common/SectionErrorBoundary"; +import ChartSection from "./components/chart/ChartSection"; +import MyCredit from "./components/credit/MyCredit"; +import DonationSection from "./components/donation/DonationSection"; + +const ListPage = () => { + return ( + <> + + + + + + + + + ); +}; + +export default ListPage; diff --git a/src/pages/MyPage/components/IdolCard.jsx b/src/pages/MyPage/components/IdolCard.jsx new file mode 100644 index 0000000..efe5907 --- /dev/null +++ b/src/pages/MyPage/components/IdolCard.jsx @@ -0,0 +1,63 @@ +import MypageDelete from "@/assets/svg/MypageDeleteSvg"; +import IdolCircleImage from "@/components/common/IdolCircleImage"; +import * as S from "./IdolCard.style"; + +const IdolCard = ({ + idol, + $size, + onRemove, + showDeleteButton = false, + $selected = false, + onClick, +}) => { + const handleClick = () => { + if (onClick) { + onClick(idol.id); + } + }; + + return ( + + {/* 이미지 영역 */} + + + + {/* 삭제 버튼 */} + {showDeleteButton && ( + { + e.stopPropagation(); + onRemove(idol.id); + }} + aria-label="삭제" + > + + + )} + + {/* 체크 표시 */} + {$selected && ( + + + + + + )} + + + {/* 텍스트 영역 */} + + {idol.name} + {idol.group} + + + ); +}; + +export default IdolCard; diff --git a/src/pages/MyPage/components/IdolCard.style.js b/src/pages/MyPage/components/IdolCard.style.js new file mode 100644 index 0000000..02560d8 --- /dev/null +++ b/src/pages/MyPage/components/IdolCard.style.js @@ -0,0 +1,96 @@ +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +// 카드 전체 컨테이너 +export const CardContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + cursor: ${({ $clickable }) => ($clickable ? "pointer" : "default")}; + + &:hover { + opacity: ${({ $clickable }) => ($clickable ? "0.9" : "1")}; + } +`; + +// 이미지 wrapper (외곽 border) +export const ImageWrapper = styled.div` + display: flex; + position: relative; + aspect-ratio: 1; + border-radius: 50%; + justify-content: center; + align-items: center; + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 50%; + background: linear-gradient(180deg, var(--color-primary) 0%, ${hexToRgba("#fe578f")} 100%); + opacity: ${({ $selected }) => ($selected ? "0.3" : "0")}; + pointer-events: none; + transition: opacity 0.2s ease; + } +`; + +// 삭제 버튼 +export const DeleteButton = styled.button` + display: flex; + position: absolute; + top: 0; + right: 0; + width: 31px; + height: 31px; + padding: 0; + border: 3px solid ${hexToRgba("#02000e")}; + + background-color: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; + border-radius: 50%; + justify-content: center; + align-items: center; + cursor: pointer; + + &:hover { + opacity: 0.8; + } +`; + +// 체크 아이콘 +export const CheckIcon = styled.div` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + justify-content: center; + align-items: center; + z-index: 10; +`; + +// 텍스트 정보 섹션 +export const InfoSection = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; +`; + +// 멤버 이름 +export const MemberName = styled.div` + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; + ${TYPO.body16Bold}; +`; + +// 그룹 이름 +export const GroupName = styled.div` + padding: 0 12px; + + color: ${hexToRgba("#FFFFFF99")}; + ${TYPO.body14Medium}; +`; diff --git a/src/pages/MyPage/index.jsx b/src/pages/MyPage/index.jsx new file mode 100644 index 0000000..91c3afa --- /dev/null +++ b/src/pages/MyPage/index.jsx @@ -0,0 +1,318 @@ +import { getIdolList } from "@/api/idolsClient"; +import { getRecommendations } from "@/api/recommendationClient"; +import MypageAdd from "@/assets/svg/MypageAddSvg"; +import ArrowButton from "@/components/common/ArrowButton"; +import HighlightButton from "@/components/common/HighlightButton"; +import useDraggableSlider from "@/hooks/useDraggableSlider"; +import { idolsStorage } from "@/storage/idols.storage"; +import { useEffect, useState } from "react"; +import { css } from "styled-components"; +import IdolCard from "./components/IdolCard"; +import * as S from "./index.style"; + +const MyPage = () => { + // 초기 화면 크기 감지 함수 + const getInitialLayout = () => { + if (typeof window === "undefined") + return { idolsPerPage: 16, recommendCount: 8, isMobile: false }; + + const isTablet = window.matchMedia("(min-width: 744px) and (max-width: 1199px)").matches; + const isDesktop = window.matchMedia("(min-width: 1200px)").matches; + + if (isDesktop) { + return { idolsPerPage: 16, recommendCount: 8, isMobile: false }; // 8*2, 첫 행만 + } else if (isTablet) { + return { idolsPerPage: 8, recommendCount: 8, isMobile: false }; // 4*2, 첫 페이지 전체 + } else { + return { idolsPerPage: 6, recommendCount: 6, isMobile: true }; // 3*2, 첫 페이지 전체 + } + }; + + const initialLayout = getInitialLayout(); + + // 화면 크기에 따른 페이지당 아이돌 수 및 추천 개수 + const [idolsPerPage, setIdolsPerPage] = useState(initialLayout.idolsPerPage); + const [recommendCount, setRecommendCount] = useState(initialLayout.recommendCount); + const [isMobile, setIsMobile] = useState(initialLayout.isMobile); + + // 미디어 쿼리 감지 + useEffect(() => { + const updateLayout = () => { + const isTablet = window.matchMedia("(min-width: 744px) and (max-width: 1199px)").matches; + const isDesktop = window.matchMedia("(min-width: 1200px)").matches; + + if (isDesktop) { + setIdolsPerPage(14); // 8*2 + setRecommendCount(7); // 첫 행만 + setIsMobile(false); + } else if (isTablet) { + setIdolsPerPage(8); // 4*2 + setRecommendCount(8); // 첫 페이지 전체 + setIsMobile(false); + } else { + // 모바일 + setIdolsPerPage(6); // 3*2 + setRecommendCount(6); // 첫 페이지 전체 + setIsMobile(true); + } + }; + + updateLayout(); + window.addEventListener("resize", updateLayout); + return () => window.removeEventListener("resize", updateLayout); + }, []); + // 상태 관리: 사용자가 선택한 관심 아이돌 ID 목록 + const [selectedIdols, setSelectedIdols] = useState(() => { + // 초기값을 로컬스토리지에서 불러오기 + return idolsStorage.get(); + }); + + // 추가하려고 선택 중인 아이돌 ID 목록 + const [selectedIds, setSelectedIds] = useState([]); + + // API로 불러온 전체 아이돌 목록 + const [allIdols, setAllIdols] = useState([]); + + // AI 추천 아이돌 목록 + const [recommendedIdols, setRecommendedIdols] = useState([]); + + // 로딩 및 에러 상태 + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + // 현재 페이지 (추가 가능한 아이돌 목록용) + const [currentPage, setCurrentPage] = useState(0); + + // 컴포넌트 마운트 시 아이돌 목록 불러오기 + useEffect(() => { + const fetchIdols = async () => { + setIsLoading(true); + setError(null); + try { + const data = await getIdolList({ pageSize: 100 }); // 충분히 큰 페이지 사이즈로 전체 목록 가져오기 + setAllIdols(data.list || []); + } catch (err) { + console.error("아이돌 목록을 불러오는데 실패했습니다:", err); + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + fetchIdols(); + }, []); + + // selectedIdols 변경 시 로컬스토리지에 저장 + useEffect(() => { + idolsStorage.set(selectedIdols); + }, [selectedIdols]); + + // 관심 아이돌 변경 시 AI 추천 업데이트 + useEffect(() => { + const fetchRecommendations = async () => { + // 관심 아이돌이 없으면 추천 안 함 + if (selectedIdols.length === 0 || allIdols.length === 0) { + setRecommendedIdols([]); + return; + } + + try { + const result = await getRecommendations(selectedIdols, recommendCount); + + const { recommended_ids } = result; + + // 추천된 ID로 아이돌 객체 찾기 + const recommended = recommended_ids + .map((id) => allIdols.find((idol) => idol.id === id)) + .filter((idol) => idol && !selectedIdols.includes(idol.id)); // 이미 선택된 건 제외 + + setRecommendedIdols(recommended); + } catch (err) { + console.error("❌ 추천을 불러오는데 실패했습니다:", err); + setRecommendedIdols([]); + } + }; + + fetchRecommendations(); + }, [selectedIdols, allIdols, recommendCount]); + + // 선택된 아이돌 객체 배열 가져오기 + const favoriteIdols = allIdols.filter((idol) => selectedIdols.includes(idol.id)); + + // 추가 가능한 아이돌 (이미 관심 아이돌에 없는 것만) + const availableIdols = allIdols.filter((idol) => !selectedIdols.includes(idol.id)); + + // AI 추천이 있으면 첫 페이지에 우선 배치, 나머지는 그 뒤에 + const availableIdolsWithRecommendations = + selectedIdols.length > 0 && recommendedIdols.length > 0 + ? [ + ...recommendedIdols.slice(0, recommendCount), // AI 추천 (화면 크기에 따라 조정) + ...availableIdols.filter((idol) => !recommendedIdols.some((rec) => rec.id === idol.id)), + ] + : availableIdols; + + // 현재 페이지의 아이돌들 (데스크톱/타블렛은 페이징, 모바일은 전체) + const currentPageIdols = isMobile + ? availableIdolsWithRecommendations // 모바일: 전체 보여주기 + : availableIdolsWithRecommendations.slice( + currentPage * idolsPerPage, + (currentPage + 1) * idolsPerPage + ); + + // 전체 페이지 수 + const totalPages = Math.ceil(availableIdolsWithRecommendations.length / idolsPerPage); + + // 슬라이드 기능 (모바일용) + const drag = useDraggableSlider(availableIdolsWithRecommendations.length); + + // 관심 아이돌 삭제 핸들러 + const handleRemoveIdol = (idolId) => { + setSelectedIdols((prev) => prev.filter((id) => id !== idolId)); + }; + + // 아이돌 선택/해제 핸들러 + const handleSelectIdol = (idolId) => { + setSelectedIds((prev) => + prev.includes(idolId) ? prev.filter((id) => id !== idolId) : [...prev, idolId] + ); + }; + + // 추가하기 버튼 핸들러 + const handleAddIdols = () => { + if (selectedIds.length === 0) return; + + setSelectedIdols((prev) => [...prev, ...selectedIds]); + setSelectedIds([]); // 선택 상태 초기화 + }; + + // 이전 페이지 + const handlePrevPage = () => { + if (currentPage > 0) { + setCurrentPage((prev) => prev - 1); + } + }; + + // 다음 페이지 + const handleNextPage = () => { + if (currentPage < totalPages - 1) { + setCurrentPage((prev) => prev + 1); + } + }; + + // 로딩 중일 때 + if (isLoading) { + return ( + + 아이돌 목록을 불러오는 중... + + ); + } + + // 에러 발생 시 + if (error) { + return ( + + 오류가 발생했습니다: {error} + + ); + } + + return ( + <> + + {/* 내가 관심있는 아이돌 섹션 */} + + 내가 관심있는 아이돌 + + {favoriteIdols.length > 0 ? ( + favoriteIdols.map((idol) => ( + + )) + ) : ( + 관심 아이돌을 추가해보세요! + )} + + + + {/* 구분선 */} + + + {/* 관심 있는 아이돌 추가 섹션 */} + + 관심 있는 아이돌들을 추가해보세요. + {/* 좌측 화살표 버튼 - 모바일에서 숨김 */} + + + + + {/* 아이돌 그리드 */} + + {currentPageIdols.map((idol) => ( + handleSelectIdol(idol.id)} + /> + ))} + + + + + + + 추가하기 + + + + + ); +}; + +export default MyPage; diff --git a/src/pages/MyPage/index.style.js b/src/pages/MyPage/index.style.js new file mode 100644 index 0000000..b3e8a87 --- /dev/null +++ b/src/pages/MyPage/index.style.js @@ -0,0 +1,179 @@ +import { media } from "@/styles/media"; +import { TYPO } from "@/styles/typography"; +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +// 전체 컨테이너 - max-width 1200px, flex column, gap 40px +export const MypageContainer = styled.div` + display: flex; + flex-direction: column; + gap: 40px; + max-width: 1200px; + margin: 0 auto; + padding: 76px 20px 40px; + min-height: 100vh; /* 항상 최소 높이 확보로 스크롤바 공간 유지 */ + + @media ${media.tablet} { + padding: 76px 40px 40px; + } + + @media ${media.desktop} { + padding: 76px 0 40px; + } +`; + +// 섹션 공통 +export const FavoriteSection = styled.section` + display: flex; + flex-direction: column; +`; + +export const AddIdolsSection = styled.section` + display: flex; + flex-direction: column; +`; + +// 섹션 제목 - 아래 마진 32px +export const SectionTitle = styled.h2` + ${TYPO.title18SemiBold}; + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; + margin-bottom: 24px; + + @media ${media.tablet} { + font-size: 20px; + margin-bottom: 28px; + } + + @media ${media.desktop} { + font-size: 24px; + margin-bottom: 32px; + } +`; + +// 내가 관심있는 아이돌 리스트 +export const FavoriteIdolsList = styled.div` + display: flex; + gap: 12px; + flex-wrap: wrap; + padding: 16px; + min-height: 100px; + + @media ${media.tablet} { + gap: 14px; + padding: 18px; + } + + @media ${media.desktop} { + gap: 16px; + padding: 20px; + } +`; + +// 빈 상태 메시지 +export const EmptyMessage = styled.div` + width: 100%; + padding: 40px 0; + + color: ${hexToRgba("#FFFFFF99")}; + font-size: 16px; + text-align: center; +`; + +// 구분선 +export const Divider = styled.div` + width: 100%; + height: 1px; + + background-color: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; +`; + +// 아이돌 그리드 컨테이너 (화살표 버튼 포함) +export const IdolsGridContainer = styled.div` + position: relative; + display: flex; + align-items: center; + gap: 0; /* 모바일: 화살표 없음 */ + overflow: hidden; /* 모바일 슬라이드용 */ + width: 100%; + + @media ${media.tablet} { + gap: 24px; + } + + @media ${media.desktop} { + gap: 32px; + } +`; + +// 화살표 버튼 +export const ArrowButton = styled.button` + display: flex; + width: 29px; + height: 135px; + border: none; + + background-color: ${hexToRgba("#1B1B1BCC")}; + border-radius: 8px; + justify-content: center; + align-items: center; + cursor: pointer; + flex-shrink: 0; + transform: ${(props) => (props.$isRight ? "scaleX(-1)" : "none")}; + + &:disabled { + opacity: 0.3; + cursor: not-allowed; + } + + &:hover:not(:disabled) { + opacity: 0.8; + } +`; + +// 아이돌 그리드 - 반응형 (모바일 3열, 타블렛 4열, 데스크톱 8열) +export const IdolsGrid = styled.div` + /* 모바일: 3*2 그리드를 유지하면서 가로 슬라이드 */ + display: grid; + gap: 32px 20px; + flex: 1; + + /* 모바일: 2행 고정, 열은 자동으로 늘어남 */ + grid-template-rows: ${(props) => (props.$isMobile ? "repeat(2, 1fr)" : "none")}; + grid-template-columns: ${(props) => (props.$isMobile ? "none" : "repeat(3, 1fr)")}; + grid-auto-flow: ${(props) => (props.$isMobile ? "column" : "row")}; + grid-auto-columns: ${(props) => (props.$isMobile ? "128px" : "auto")}; /* 카드 너비 */ + + @media ${media.tablet} { + grid-template-rows: none; + grid-template-columns: repeat(4, 1fr); /* 타블렛: 4*2 */ + grid-auto-flow: row; + grid-auto-columns: auto; + gap: 32px 20px; + will-change: auto; + } + + @media ${media.desktop} { + grid-template-rows: none; + grid-template-columns: repeat(7, 1fr); /* 데스크톱: 8*2 */ + grid-auto-flow: row; + grid-auto-columns: auto; + gap: 32px 20px; + will-change: auto; + } +`; + +export const Wrapper = styled.div` + display: flex; + position: relative; + align-items: center; + + @media ${media.desktop} { + gap: 12px; + width: 1200px; + padding: 0 12px; + } + + @media ${media.desktopSlider} { + gap: 40px; + } +`; diff --git a/src/pages/NotFoundPage/index.jsx b/src/pages/NotFoundPage/index.jsx new file mode 100644 index 0000000..b4433af --- /dev/null +++ b/src/pages/NotFoundPage/index.jsx @@ -0,0 +1,24 @@ +import { useNavigate } from "react-router-dom"; +import * as S from "./index.style"; + +const NotFoundPage = () => { + const navigate = useNavigate(); + + const handleGoHome = () => { + navigate("/", { replace: true }); + }; + + return ( + + 404 + 페이지를 찾을 수 없습니다 + + 입력하신 주소가 잘못되었거나 +
페이지가 이동되었을 수 있어요. +
+ 홈으로 돌아가기 +
+ ); +}; + +export default NotFoundPage; diff --git a/src/pages/NotFoundPage/index.style.js b/src/pages/NotFoundPage/index.style.js new file mode 100644 index 0000000..4da9df0 --- /dev/null +++ b/src/pages/NotFoundPage/index.style.js @@ -0,0 +1,55 @@ +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Container = styled.section` + width: 100%; + min-height: calc(100vh - 80px); + padding: 40px 20px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + text-align: center; +`; + +export const Title = styled.h1` + font-size: 72px; + font-weight: 800; + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; + margin: 0; +`; + +export const Subtitle = styled.h2` + font-size: 24px; + font-weight: 700; + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}; +`; + +export const Description = styled.p` + font-size: 16px; + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}; + line-height: 1.6; + margin-bottom: 20px; +`; + +export const HomeButton = styled.button` + padding: 12px 20px; + border-radius: 8px; + color: white; + font-size: 16px; + font-weight: 700; + border: none; + cursor: pointer; + transition: 0.2s; + background: linear-gradient( + to right, + ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}, + ${hexToRgba(COLOR_VAR_MAP["--color-secondary"])} + ); + + &:hover { + transform: translateY(-2px); + opacity: 0.9; + } +`; diff --git a/src/pages/RouteErrorPage/index.jsx b/src/pages/RouteErrorPage/index.jsx new file mode 100644 index 0000000..c66d6c0 --- /dev/null +++ b/src/pages/RouteErrorPage/index.jsx @@ -0,0 +1,37 @@ +import { useRouteError, useNavigate } from "react-router-dom"; +import * as S from "./index.style"; + +const RouteErrorPage = () => { + const error = useRouteError(); + const navigate = useNavigate(); + + console.error("[RouteErrorPage]", error); + + const message = + error?.statusText || + error?.message || + "알 수 없는 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."; + + return ( + + 페이지를 불러오는 중 문제가 발생했어요 + + + 잠시 후 다시 시도해 주세요. +
+ 문제가 반복되면 관리자에게 문의 부탁드립니다. +
+ + {message} + + + window.location.reload()}>다시 시도하기 + navigate("/", { replace: true })}> + 홈으로 돌아가기 + + +
+ ); +}; + +export default RouteErrorPage; diff --git a/src/pages/RouteErrorPage/index.style.js b/src/pages/RouteErrorPage/index.style.js new file mode 100644 index 0000000..e199d75 --- /dev/null +++ b/src/pages/RouteErrorPage/index.style.js @@ -0,0 +1,85 @@ +import { COLOR_VAR_MAP, hexToRgba } from "@/utils/color"; +import styled from "styled-components"; + +export const Container = styled.section` + width: 100%; + height: calc(100vh - 80px); + padding: 40px 20px; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + text-align: center; + gap: 20px; +`; + +export const Title = styled.h1` + font-size: 24px; + font-weight: 700; + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; +`; + +export const Description = styled.p` + font-size: 16px; + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-90"])}; + line-height: 1.6; +`; + +export const ErrorBox = styled.pre` + margin-top: 12px; + background: rgb(0 0 0 / 40%); + color: ${hexToRgba("#ff7b7b")}; + padding: 16px 20px; + border-radius: 8px; + font-size: 14px; + white-space: pre-wrap; + max-width: 420px; + width: 100%; +`; + +export const ButtonGroup = styled.div` + margin-top: 16px; + display: flex; + gap: 12px; + justify-content: center; +`; + +export const RetryButton = styled.button` + padding: 12px 20px; + font-size: 15px; + font-weight: 700; + border-radius: 8px; + border: none; + cursor: pointer; + + background: ${hexToRgba(COLOR_VAR_MAP["--color-gray-500"])}; + color: ${hexToRgba(COLOR_VAR_MAP["--color-white-100"])}; + + &:hover { + opacity: 0.9; + transform: translateY(-2px); + } +`; + +export const HomeButton = styled.button` + padding: 12px 20px; + font-size: 15px; + font-weight: 700; + border-radius: 8px; + border: none; + cursor: pointer; + + background: linear-gradient( + to right, + ${hexToRgba(COLOR_VAR_MAP["--color-primary"])}, + ${hexToRgba(COLOR_VAR_MAP["--color-secondary"])} + ); + color: white; + + &:hover { + opacity: 0.9; + transform: translateY(-2px); + } +`; diff --git a/src/storage/credit.storage.js b/src/storage/credit.storage.js new file mode 100644 index 0000000..f818ece --- /dev/null +++ b/src/storage/credit.storage.js @@ -0,0 +1,8 @@ +const KEY = "APP_CREDIT"; + +export const creditStorage = { + get: () => Number(localStorage.getItem(KEY)), + set: (credit) => { + localStorage.setItem(KEY, credit); + }, +}; diff --git a/src/storage/idols.storage.js b/src/storage/idols.storage.js new file mode 100644 index 0000000..d3f7df5 --- /dev/null +++ b/src/storage/idols.storage.js @@ -0,0 +1,11 @@ +const KEY = "APP_FAVORITE_IDOLS"; + +export const idolsStorage = { + get: () => { + const data = localStorage.getItem(KEY); + return data ? JSON.parse(data) : []; + }, + set: (idolIds) => { + localStorage.setItem(KEY, JSON.stringify(idolIds)); + }, +}; diff --git a/src/styles/globalStyle.js b/src/styles/globalStyle.js index 755aa28..ad7a342 100644 --- a/src/styles/globalStyle.js +++ b/src/styles/globalStyle.js @@ -2,26 +2,30 @@ import { createGlobalStyle } from "styled-components"; export const GlobalStyle = createGlobalStyle` :root { - /* black */ - --black-02000E: #02000E; - --black-181D26: #181D26; - /* yellow */ - --yellow-Dec030: #Dec030; - /* brand */ - --orange-F96D69: #F96D69; - --pink-FE5493: #FE5493; - /* gray */ - --gray-67666E: #67666E; - --gray-828282: #828282; - --gray-8C92AB: #8C92AB; - --gray-A3A5A8: #A3A5A8; - /* white */ - --white-FFFFFF: #FFFFFF; - --white-F7F7F8: #F7F7F8; + /* Background */ + --color-bg-dark: #02000E; + --color-bg-base: #181D26; + + /* Gray Scale */ + --color-gray-600: #67666E; + --color-gray-500: #828282; + --color-gray-400: #8C92AB; + --color-gray-300: #A3A5A8; + + /* Brand */ + --color-primary: #F96D69; + --color-secondary: #FE5493; + --color-accent: #DEC030; + + /* Neutral */ + --color-white-100: #FFF; /* 가장 밝은 텍스트 */ + --color-white-90: #F7F7F8; /* 약간 약한 텍스트 */ } + @font-face { - font-family: "Pretendard"; + font-family: Pretendard; + font-weight: 900; font-display: swap; src: local("Pretendard Black"), @@ -30,7 +34,8 @@ export const GlobalStyle = createGlobalStyle` } @font-face { - font-family: "Pretendard"; + font-family: Pretendard; + font-weight: 800; font-display: swap; src: local("Pretendard ExtraBold"), @@ -39,7 +44,8 @@ export const GlobalStyle = createGlobalStyle` } @font-face { - font-family: "Pretendard"; + font-family: Pretendard; + font-weight: 700; font-display: swap; src: local("Pretendard Bold"), @@ -48,7 +54,8 @@ export const GlobalStyle = createGlobalStyle` } @font-face { - font-family: "Pretendard"; + font-family: Pretendard; + font-weight: 600; font-display: swap; src: local("Pretendard SemiBold"), @@ -57,7 +64,8 @@ export const GlobalStyle = createGlobalStyle` } @font-face { - font-family: "Pretendard"; + font-family: Pretendard; + font-weight: 500; font-display: swap; src: local("Pretendard Medium"), @@ -66,7 +74,8 @@ export const GlobalStyle = createGlobalStyle` } @font-face { - font-family: "Pretendard"; + font-family: Pretendard; + font-weight: 400; font-display: swap; src: local("Pretendard Regular"), @@ -75,7 +84,8 @@ export const GlobalStyle = createGlobalStyle` } @font-face { - font-family: "Pretendard"; + font-family: Pretendard; + font-weight: 300; font-display: swap; src: local("Pretendard Light"), @@ -84,7 +94,8 @@ export const GlobalStyle = createGlobalStyle` } @font-face { - font-family: "Pretendard"; + font-family: Pretendard; + font-weight: 200; font-display: swap; src: local("Pretendard ExtraLight"), @@ -93,7 +104,8 @@ export const GlobalStyle = createGlobalStyle` } @font-face { - font-family: "Pretendard"; + font-family: Pretendard; + font-weight: 100; font-display: swap; src: local("Pretendard Thin"), @@ -103,13 +115,14 @@ export const GlobalStyle = createGlobalStyle` * { margin: 0; - font-family: "Pretendard", sans-serif !important; + font-family: Pretendard, sans-serif !important; box-sizing: border-box; } html { - background: var(--black-02000E); - color: var(--white-F7F7F8); + background: var(--color-bg-dark); + + color: var(--color-white-90); font-size: 10px; } @@ -123,8 +136,9 @@ export const GlobalStyle = createGlobalStyle` h4, h5, h6 { - line-height: 2.6rem; margin: 0; + + line-height: 2.6rem; } a { @@ -147,8 +161,10 @@ export const GlobalStyle = createGlobalStyle` input, select, textarea { - font-family: inherit; border: none; + + color: var(--color-white-90); + font-family: inherit; background: none; outline: none; } diff --git a/src/styles/media.js b/src/styles/media.js new file mode 100644 index 0000000..e6dc3b0 --- /dev/null +++ b/src/styles/media.js @@ -0,0 +1,5 @@ +export const media = { + tablet: "(min-width: 744px)", + desktop: "(min-width: 1200px)", + desktopSlider: "(min-width: 1400px)", +}; diff --git a/src/styles/typography.js b/src/styles/typography.js new file mode 100644 index 0000000..f496e2e --- /dev/null +++ b/src/styles/typography.js @@ -0,0 +1,67 @@ +import { css } from "styled-components"; + +export const TYPO = { + // Large Titles + titleXL: css` + font-size: 3rem; /* 30px */ + font-weight: 700; + line-height: 3.2rem; + `, + titleLg: css` + font-size: 2.6rem; /* 26px */ + font-weight: 700; + line-height: 3rem; + `, + titleMd: css` + font-size: 2.4rem; /* 24px */ + font-weight: 700; + line-height: 2.8rem; + `, + + // Mid Titles + title20Bold: css` + font-size: 2rem; /* 20px */ + font-weight: 700; + `, + title20Regular: css` + font-size: 2rem; + font-weight: 400; + `, + title18SemiBold: css` + font-size: 1.8rem; /* 18px */ + font-weight: 600; + `, + + // Body Text + body16Medium: css` + font-size: 1.6rem; /* 16px */ + font-weight: 500; + `, + body16Bold: css` + font-size: 1.6rem; /* 16px */ + font-weight: 500; + `, + body14Bold: css` + font-size: 1.4rem; /* 14px */ + font-weight: 700; + `, + body14Medium: css` + font-size: 1.4rem; + font-weight: 500; + `, + + // Captions + caption12Regular: css` + font-size: 1.2rem; /* 12px */ + font-weight: 400; + line-height: 1.8rem; + `, + caption12SemiBold: css` + font-size: 1.2rem; + font-weight: 600; + `, + caption14Bold: css` + font-size: 1.4rem; /* 14px → from 13px */ + font-weight: 700; + `, +}; diff --git a/src/utils/color.js b/src/utils/color.js new file mode 100644 index 0000000..c7e6313 --- /dev/null +++ b/src/utils/color.js @@ -0,0 +1,66 @@ +export const COLOR_VAR_MAP = { + "--color-bg-dark": "#02000E", + "--color-bg-base": "#181D26", + + "--color-gray-600": "#67666E", + "--color-gray-500": "#828282", + "--color-gray-400": "#8C92AB", + "--color-gray-300": "#A3A5A8", + + "--color-primary": "#F96D69", + "--color-secondary": "#FE5493", + "--color-accent": "#DEC030", + + "--color-white-100": "#FFFFFF", + "--color-white-90": "#F7F7F8", +}; + +/** + * HEX 색상을 rgba로 변환 + * @param {string} hex - #fff, #ffff, #ffffff, #ffffffff + * @param {number} [opacity] - 0 ~ 1 범위의 alpha 값(optional) + */ +export const hexToRgba = (hex, opacity = null) => { + let value = hex.trim(); + if (value.startsWith("#")) { + value = value.slice(1); + } + + let r, g, b, a; + + if (value.length === 3) { + // #rgb + r = value[0] + value[0]; + g = value[1] + value[1]; + b = value[2] + value[2]; + a = "ff"; + } else if (value.length === 4) { + // #rgba + r = value[0] + value[0]; + g = value[1] + value[1]; + b = value[2] + value[2]; + a = value[3] + value[3]; + } else if (value.length === 6) { + // #rrggbb + r = value.slice(0, 2); + g = value.slice(2, 4); + b = value.slice(4, 6); + a = "ff"; + } else if (value.length === 8) { + // #rrggbbaa + r = value.slice(0, 2); + g = value.slice(2, 4); + b = value.slice(4, 6); + a = value.slice(6, 8); + } + + const rDec = parseInt(r, 16); + const gDec = parseInt(g, 16); + const bDec = parseInt(b, 16); + const aDec = parseInt(a, 16) / 255; + + // opacity 파라미터가 있으면 우선 적용 + const finalAlpha = opacity !== null ? opacity : Number(aDec.toFixed(3)); + + return `rgba(${rDec}, ${gDec}, ${bDec}, ${finalAlpha})`; +}; diff --git a/src/utils/date.js b/src/utils/date.js new file mode 100644 index 0000000..e66c9ce --- /dev/null +++ b/src/utils/date.js @@ -0,0 +1,7 @@ +// 남은 일수 계산 +export const getRemainingDays = (deadline) => { + const now = new Date(); + const deadlineDate = new Date(deadline); + const diffTime = deadlineDate - now; + return diffTime > 0 ? Math.ceil(diffTime / (1000 * 60 * 60 * 24)) : 0; +}; diff --git a/src/utils/number.js b/src/utils/number.js new file mode 100644 index 0000000..de878f7 --- /dev/null +++ b/src/utils/number.js @@ -0,0 +1,4 @@ +// 양수의 정수인지 확인 +export const isPositiveInteger = (value) => { + return /^[1-9][0-9]*$/.test(value); +};