diff --git a/.env b/.env index f82651ed..c6bda04c 100644 --- a/.env +++ b/.env @@ -1,2 +1,6 @@ VITE_BASE_URL=https://api.humanzipyo.com -VITE_GOOGLE_ANALYTICS=G-EZPQMV95QJ \ No newline at end of file +VITE_GOOGLE_ANALYTICS=G-EZPQMV95QJ +VITE_KAKAO_API_KEY=6c656322cc1bcb9669fbaee86b9df89a +VITE_APPLE_CLIENT_ID=humanzipyo.app.com +VITE_GOOGLE_CLIENT_ID=376774774273-5do2k4e5r3k13mgjdfam4csprajporr9.apps.googleusercontent.com +VITE_NAVER_CLIENT_ID=c943YPmMR8bflLezMJGz diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..c9a921ec --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + pull_request: + branches: ['main', 'develop'] + push: + branches: ['develop'] + +jobs: + lint-and-format: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18.20.5' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Check Prettier formatting + run: npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css}" + + - name: Type check + run: npx tsc --noEmit diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index e6e879a6..97bab53a 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -34,7 +34,7 @@ jobs: cache: 'npm' - name: Install dependencies - run: npm install + run: npm ci - name: Build the project run: npm run build diff --git a/.gitignore b/.gitignore index a547bf36..1677f86b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,13 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* +*.tsbuildinfo + node_modules dist dist-ssr *.local +.env # Editor directories and files .vscode/* diff --git a/.prettierrc b/.prettierrc index b699b24e..82d89519 100644 --- a/.prettierrc +++ b/.prettierrc @@ -49,5 +49,5 @@ ], "importOrderSeparation": false, "importOrderSortSpecifiers": true, - "plugins": ["prettier-plugin-css-order", "@trivago/prettier-plugin-sort-imports"] + "plugins": ["@trivago/prettier-plugin-sort-imports"] } diff --git a/README.md b/README.md index c7c14614..5afdbdaf 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,7 @@ ![TypeScript](https://img.shields.io/badge/typescript-3178C6.svg?style=flat-square&logo=typescript&logoColor=white) ![react](https://img.shields.io/badge/react-61DAFB.svg?style=flat-square&logo=react&logoColor=black) ![vite](https://img.shields.io/badge/vite-646CFF.svg?style=flat-square&logo=vite&logoColor=white) - ![reactquery](https://img.shields.io/badge/reactquery-FF4154.svg?style=flat-square&logo=reactquery&logoColor=white) - ![ESLint](https://img.shields.io/badge/ESLint-4B32C3.svg?style=flat-square&logo=ESLint&logoColor=white) ![prettier](https://img.shields.io/badge/prettier-F7B93E.svg?style=flat-square&logo=prettier&logoColor=black) diff --git a/asconfig.json b/asconfig.json deleted file mode 100644 index 491904df..00000000 --- a/asconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "targets": { - "debug": { - "outFile": "build/debug.wasm", - "textFile": "build/debug.wat", - "sourceMap": true, - "debug": true - }, - "release": { - "outFile": "./assembly/build/release.wasm", - "textFile": "./assembly/build/release.wat", - "sourceMap": true, - "optimizeLevel": 3, - "shrinkLevel": 0, - "converge": false, - "noAssert": false - } - }, - "options": { - "bindings": "esm" - } -} diff --git a/eslint.config.js b/eslint.config.js index 9fc58d6c..236f4a7c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,40 +4,38 @@ import globals from 'globals'; import tseslint from 'typescript-eslint'; export default [ + { + ignores: [ + 'dist/**', + 'public/assets/**', + 'node_modules/**', + 'build/**', + '*.config.js', + '*.config.ts', + 'src/utils/test.js', + 'src/utils/worker/**', + 'src/utils/wasm/**', + ], + }, { files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'] }, { languageOptions: { globals: globals.browser } }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + pluginReact.configs.flat.recommended, { - 'jsx-runtime': { - env: { - browser: true, - es6: true, - node: true, - }, - plugins: ['@typescript-eslint', 'react', 'prettier'], - extends: [ - 'prettier', - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:prettier/recommended', - 'plugin:@typescript-eslint/recommended', - ], - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: './tsconfig.json', - }, - rules: { - 'prettier/prettier': 'error', - 'react/react-in-jsx-scope': 0, - 'react/prefer-stateless-function': 0, - 'react/jsx-filename-extension': 0, - 'react/jsx-one-expression-per-line': 0, - 'no-nested-ternary': 0, + settings: { + react: { + version: 'detect', }, }, + rules: { + 'react/react-in-jsx-scope': 0, + 'react/prefer-stateless-function': 0, + 'react/jsx-filename-extension': 0, + 'react/jsx-one-expression-per-line': 0, + 'no-nested-ternary': 0, + 'react/prop-types': 0, + '@typescript-eslint/no-explicit-any': 'warn', + }, }, - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - pluginReact.configs.flat.recommended, ]; diff --git a/index.html b/index.html index b83c8513..677c142c 100644 --- a/index.html +++ b/index.html @@ -21,7 +21,7 @@ 인간지표 : 주식투자심리도우미 -
- +
+ diff --git a/netlify.toml b/netlify.toml index 8e56e158..6d2ab888 100644 --- a/netlify.toml +++ b/netlify.toml @@ -18,4 +18,14 @@ cache-control = ''' max-age=31536000, immutable - ''' \ No newline at end of file + ''' + +[[headers]] + for = "/.well-known/apple-app-site-association" + [headers.values] + Content-Type = "application/json" + +[[headers]] + for = "/.well-known/assetlinks.json" + [headers.values] + Content-Type = "application/json" \ No newline at end of file diff --git a/out.wat b/out.wat new file mode 100644 index 00000000..f4bf116f --- /dev/null +++ b/out.wat @@ -0,0 +1,3805 @@ +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32) (result i32))) + (type (;2;) (func)) + (type (;3;) (func (param i32))) + (type (;4;) (func (param i32 i32))) + (type (;5;) (func (param i32 i32 i32) (result i32))) + (type (;6;) (func (param i32 i32) (result i32))) + (func (;0;) (type 2) + call 10) + (func (;1;) (type 4) (param i32 i32) + (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) + global.get 0 + local.set 2 + i32.const 16 + local.set 3 + local.get 2 + local.get 3 + i32.sub + local.set 4 + local.get 4 + global.set 0 + local.get 4 + local.get 0 + i32.store offset=12 + local.get 4 + local.get 1 + i32.store offset=8 + local.get 4 + i32.load offset=12 + local.set 5 + i32.const 0 + local.set 6 + local.get 6 + local.get 5 + i32.store offset=67768 + local.get 4 + i32.load offset=8 + local.set 7 + i32.const 0 + local.set 8 + local.get 8 + local.get 7 + i32.store offset=67772 + i32.const 0 + local.set 9 + local.get 9 + i32.load offset=67768 + local.set 10 + i32.const 1 + local.set 11 + local.get 10 + local.get 11 + i32.add + local.set 12 + i32.const 0 + local.set 13 + local.get 13 + local.get 12 + i32.store offset=67776 + i32.const 0 + local.set 14 + local.get 14 + i32.load offset=67772 + local.set 15 + i32.const 1 + local.set 16 + local.get 15 + local.get 16 + i32.add + local.set 17 + i32.const 0 + local.set 18 + local.get 18 + local.get 17 + i32.store offset=67780 + i32.const 0 + local.set 19 + local.get 19 + i32.load offset=67776 + local.set 20 + i32.const 2 + local.set 21 + local.get 20 + local.get 21 + i32.shl + local.set 22 + i32.const 0 + local.set 23 + local.get 23 + i32.load offset=67780 + local.set 24 + local.get 22 + local.get 24 + i32.mul + local.set 25 + local.get 25 + call 4 + local.set 26 + i32.const 0 + local.set 27 + local.get 27 + local.get 26 + i32.store offset=67784 + i32.const 0 + local.set 28 + local.get 28 + i32.load offset=67768 + local.set 29 + i32.const 0 + local.set 30 + local.get 29 + local.get 30 + i32.shl + local.set 31 + i32.const 0 + local.set 32 + local.get 32 + i32.load offset=67772 + local.set 33 + local.get 31 + local.get 33 + i32.mul + local.set 34 + local.get 34 + call 4 + local.set 35 + i32.const 0 + local.set 36 + local.get 36 + local.get 35 + i32.store offset=67788 + i32.const 0 + local.set 37 + local.get 37 + i32.load offset=67768 + local.set 38 + i32.const 2 + local.set 39 + local.get 38 + local.get 39 + i32.shl + local.set 40 + i32.const 0 + local.set 41 + local.get 41 + i32.load offset=67772 + local.set 42 + local.get 40 + local.get 42 + i32.mul + local.set 43 + local.get 43 + call 4 + local.set 44 + i32.const 0 + local.set 45 + local.get 45 + local.get 44 + i32.store offset=67792 + i32.const 16 + local.set 46 + local.get 4 + local.get 46 + i32.add + local.set 47 + local.get 47 + global.set 0 + return) + (func (;2;) (type 2) + block ;; label = @1 + i32.const 1 + i32.eqz + br_if 0 (;@1;) + call 0 + end) + (func (;3;) (type 0) (result i32) + i32.const 67796) + (func (;4;) (type 1) (param i32) (result i32) + (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) + global.get 0 + i32.const 16 + i32.sub + local.tee 1 + global.set 0 + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + block ;; label = @4 + block ;; label = @5 + block ;; label = @6 + block ;; label = @7 + block ;; label = @8 + block ;; label = @9 + block ;; label = @10 + block ;; label = @11 + local.get 0 + i32.const 244 + i32.gt_u + br_if 0 (;@11;) + block ;; label = @12 + i32.const 0 + i32.load offset=67800 + local.tee 2 + i32.const 16 + local.get 0 + i32.const 11 + i32.add + i32.const 504 + i32.and + local.get 0 + i32.const 11 + i32.lt_u + select + local.tee 3 + i32.const 3 + i32.shr_u + local.tee 4 + i32.shr_u + local.tee 0 + i32.const 3 + i32.and + i32.eqz + br_if 0 (;@12;) + block ;; label = @13 + block ;; label = @14 + local.get 0 + i32.const -1 + i32.xor + i32.const 1 + i32.and + local.get 4 + i32.add + local.tee 3 + i32.const 3 + i32.shl + local.tee 4 + i32.const 67840 + i32.add + local.tee 0 + local.get 4 + i32.const 67848 + i32.add + i32.load + local.tee 4 + i32.load offset=8 + local.tee 5 + i32.ne + br_if 0 (;@14;) + i32.const 0 + local.get 2 + i32.const -2 + local.get 3 + i32.rotl + i32.and + i32.store offset=67800 + br 1 (;@13;) + end + local.get 5 + local.get 0 + i32.store offset=12 + local.get 0 + local.get 5 + i32.store offset=8 + end + local.get 4 + i32.const 8 + i32.add + local.set 0 + local.get 4 + local.get 3 + i32.const 3 + i32.shl + local.tee 3 + i32.const 3 + i32.or + i32.store offset=4 + local.get 4 + local.get 3 + i32.add + local.tee 4 + local.get 4 + i32.load offset=4 + i32.const 1 + i32.or + i32.store offset=4 + br 11 (;@1;) + end + local.get 3 + i32.const 0 + i32.load offset=67808 + local.tee 6 + i32.le_u + br_if 1 (;@10;) + block ;; label = @12 + local.get 0 + i32.eqz + br_if 0 (;@12;) + block ;; label = @13 + block ;; label = @14 + local.get 0 + local.get 4 + i32.shl + i32.const 2 + local.get 4 + i32.shl + local.tee 0 + i32.const 0 + local.get 0 + i32.sub + i32.or + i32.and + i32.ctz + local.tee 4 + i32.const 3 + i32.shl + local.tee 0 + i32.const 67840 + i32.add + local.tee 5 + local.get 0 + i32.const 67848 + i32.add + i32.load + local.tee 0 + i32.load offset=8 + local.tee 7 + i32.ne + br_if 0 (;@14;) + i32.const 0 + local.get 2 + i32.const -2 + local.get 4 + i32.rotl + i32.and + local.tee 2 + i32.store offset=67800 + br 1 (;@13;) + end + local.get 7 + local.get 5 + i32.store offset=12 + local.get 5 + local.get 7 + i32.store offset=8 + end + local.get 0 + local.get 3 + i32.const 3 + i32.or + i32.store offset=4 + local.get 0 + local.get 3 + i32.add + local.tee 7 + local.get 4 + i32.const 3 + i32.shl + local.tee 4 + local.get 3 + i32.sub + local.tee 3 + i32.const 1 + i32.or + i32.store offset=4 + local.get 0 + local.get 4 + i32.add + local.get 3 + i32.store + block ;; label = @13 + local.get 6 + i32.eqz + br_if 0 (;@13;) + local.get 6 + i32.const -8 + i32.and + i32.const 67840 + i32.add + local.set 5 + i32.const 0 + i32.load offset=67820 + local.set 4 + block ;; label = @14 + block ;; label = @15 + local.get 2 + i32.const 1 + local.get 6 + i32.const 3 + i32.shr_u + i32.shl + local.tee 8 + i32.and + br_if 0 (;@15;) + i32.const 0 + local.get 2 + local.get 8 + i32.or + i32.store offset=67800 + local.get 5 + local.set 8 + br 1 (;@14;) + end + local.get 5 + i32.load offset=8 + local.set 8 + end + local.get 5 + local.get 4 + i32.store offset=8 + local.get 8 + local.get 4 + i32.store offset=12 + local.get 4 + local.get 5 + i32.store offset=12 + local.get 4 + local.get 8 + i32.store offset=8 + end + local.get 0 + i32.const 8 + i32.add + local.set 0 + i32.const 0 + local.get 7 + i32.store offset=67820 + i32.const 0 + local.get 3 + i32.store offset=67808 + br 11 (;@1;) + end + i32.const 0 + i32.load offset=67804 + local.tee 9 + i32.eqz + br_if 1 (;@10;) + local.get 9 + i32.ctz + i32.const 2 + i32.shl + i32.const 68104 + i32.add + i32.load + local.tee 7 + i32.load offset=4 + i32.const -8 + i32.and + local.get 3 + i32.sub + local.set 4 + local.get 7 + local.set 5 + block ;; label = @12 + loop ;; label = @13 + block ;; label = @14 + local.get 5 + i32.load offset=16 + local.tee 0 + br_if 0 (;@14;) + local.get 5 + i32.load offset=20 + local.tee 0 + i32.eqz + br_if 2 (;@12;) + end + local.get 0 + i32.load offset=4 + i32.const -8 + i32.and + local.get 3 + i32.sub + local.tee 5 + local.get 4 + local.get 5 + local.get 4 + i32.lt_u + local.tee 5 + select + local.set 4 + local.get 0 + local.get 7 + local.get 5 + select + local.set 7 + local.get 0 + local.set 5 + br 0 (;@13;) + end + unreachable + end + local.get 7 + i32.load offset=24 + local.set 10 + block ;; label = @12 + local.get 7 + i32.load offset=12 + local.tee 0 + local.get 7 + i32.eq + br_if 0 (;@12;) + local.get 7 + i32.load offset=8 + local.tee 5 + local.get 0 + i32.store offset=12 + local.get 0 + local.get 5 + i32.store offset=8 + br 10 (;@2;) + end + block ;; label = @12 + block ;; label = @13 + local.get 7 + i32.load offset=20 + local.tee 5 + i32.eqz + br_if 0 (;@13;) + local.get 7 + i32.const 20 + i32.add + local.set 8 + br 1 (;@12;) + end + local.get 7 + i32.load offset=16 + local.tee 5 + i32.eqz + br_if 3 (;@9;) + local.get 7 + i32.const 16 + i32.add + local.set 8 + end + loop ;; label = @12 + local.get 8 + local.set 11 + local.get 5 + local.tee 0 + i32.const 20 + i32.add + local.set 8 + local.get 0 + i32.load offset=20 + local.tee 5 + br_if 0 (;@12;) + local.get 0 + i32.const 16 + i32.add + local.set 8 + local.get 0 + i32.load offset=16 + local.tee 5 + br_if 0 (;@12;) + end + local.get 11 + i32.const 0 + i32.store + br 9 (;@2;) + end + i32.const -1 + local.set 3 + local.get 0 + i32.const -65 + i32.gt_u + br_if 0 (;@10;) + local.get 0 + i32.const 11 + i32.add + local.tee 4 + i32.const -8 + i32.and + local.set 3 + i32.const 0 + i32.load offset=67804 + local.tee 10 + i32.eqz + br_if 0 (;@10;) + i32.const 31 + local.set 6 + block ;; label = @11 + local.get 0 + i32.const 16777204 + i32.gt_u + br_if 0 (;@11;) + local.get 3 + i32.const 38 + local.get 4 + i32.const 8 + i32.shr_u + i32.clz + local.tee 0 + i32.sub + i32.shr_u + i32.const 1 + i32.and + local.get 0 + i32.const 1 + i32.shl + i32.sub + i32.const 62 + i32.add + local.set 6 + end + i32.const 0 + local.get 3 + i32.sub + local.set 4 + block ;; label = @11 + block ;; label = @12 + block ;; label = @13 + block ;; label = @14 + local.get 6 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + i32.load + local.tee 5 + br_if 0 (;@14;) + i32.const 0 + local.set 0 + i32.const 0 + local.set 8 + br 1 (;@13;) + end + i32.const 0 + local.set 0 + local.get 3 + i32.const 0 + i32.const 25 + local.get 6 + i32.const 1 + i32.shr_u + i32.sub + local.get 6 + i32.const 31 + i32.eq + select + i32.shl + local.set 7 + i32.const 0 + local.set 8 + loop ;; label = @14 + block ;; label = @15 + local.get 5 + i32.load offset=4 + i32.const -8 + i32.and + local.get 3 + i32.sub + local.tee 2 + local.get 4 + i32.ge_u + br_if 0 (;@15;) + local.get 2 + local.set 4 + local.get 5 + local.set 8 + local.get 2 + br_if 0 (;@15;) + i32.const 0 + local.set 4 + local.get 5 + local.set 8 + local.get 5 + local.set 0 + br 3 (;@12;) + end + local.get 0 + local.get 5 + i32.load offset=20 + local.tee 2 + local.get 2 + local.get 5 + local.get 7 + i32.const 29 + i32.shr_u + i32.const 4 + i32.and + i32.add + i32.load offset=16 + local.tee 11 + i32.eq + select + local.get 0 + local.get 2 + select + local.set 0 + local.get 7 + i32.const 1 + i32.shl + local.set 7 + local.get 11 + local.set 5 + local.get 11 + br_if 0 (;@14;) + end + end + block ;; label = @13 + local.get 0 + local.get 8 + i32.or + br_if 0 (;@13;) + i32.const 0 + local.set 8 + i32.const 2 + local.get 6 + i32.shl + local.tee 0 + i32.const 0 + local.get 0 + i32.sub + i32.or + local.get 10 + i32.and + local.tee 0 + i32.eqz + br_if 3 (;@10;) + local.get 0 + i32.ctz + i32.const 2 + i32.shl + i32.const 68104 + i32.add + i32.load + local.set 0 + end + local.get 0 + i32.eqz + br_if 1 (;@11;) + end + loop ;; label = @12 + local.get 0 + i32.load offset=4 + i32.const -8 + i32.and + local.get 3 + i32.sub + local.tee 2 + local.get 4 + i32.lt_u + local.set 7 + block ;; label = @13 + local.get 0 + i32.load offset=16 + local.tee 5 + br_if 0 (;@13;) + local.get 0 + i32.load offset=20 + local.set 5 + end + local.get 2 + local.get 4 + local.get 7 + select + local.set 4 + local.get 0 + local.get 8 + local.get 7 + select + local.set 8 + local.get 5 + local.set 0 + local.get 5 + br_if 0 (;@12;) + end + end + local.get 8 + i32.eqz + br_if 0 (;@10;) + local.get 4 + i32.const 0 + i32.load offset=67808 + local.get 3 + i32.sub + i32.ge_u + br_if 0 (;@10;) + local.get 8 + i32.load offset=24 + local.set 11 + block ;; label = @11 + local.get 8 + i32.load offset=12 + local.tee 0 + local.get 8 + i32.eq + br_if 0 (;@11;) + local.get 8 + i32.load offset=8 + local.tee 5 + local.get 0 + i32.store offset=12 + local.get 0 + local.get 5 + i32.store offset=8 + br 8 (;@3;) + end + block ;; label = @11 + block ;; label = @12 + local.get 8 + i32.load offset=20 + local.tee 5 + i32.eqz + br_if 0 (;@12;) + local.get 8 + i32.const 20 + i32.add + local.set 7 + br 1 (;@11;) + end + local.get 8 + i32.load offset=16 + local.tee 5 + i32.eqz + br_if 3 (;@8;) + local.get 8 + i32.const 16 + i32.add + local.set 7 + end + loop ;; label = @11 + local.get 7 + local.set 2 + local.get 5 + local.tee 0 + i32.const 20 + i32.add + local.set 7 + local.get 0 + i32.load offset=20 + local.tee 5 + br_if 0 (;@11;) + local.get 0 + i32.const 16 + i32.add + local.set 7 + local.get 0 + i32.load offset=16 + local.tee 5 + br_if 0 (;@11;) + end + local.get 2 + i32.const 0 + i32.store + br 7 (;@3;) + end + block ;; label = @10 + i32.const 0 + i32.load offset=67808 + local.tee 0 + local.get 3 + i32.lt_u + br_if 0 (;@10;) + i32.const 0 + i32.load offset=67820 + local.set 4 + block ;; label = @11 + block ;; label = @12 + local.get 0 + local.get 3 + i32.sub + local.tee 5 + i32.const 16 + i32.lt_u + br_if 0 (;@12;) + local.get 4 + local.get 3 + i32.add + local.tee 7 + local.get 5 + i32.const 1 + i32.or + i32.store offset=4 + local.get 4 + local.get 0 + i32.add + local.get 5 + i32.store + local.get 4 + local.get 3 + i32.const 3 + i32.or + i32.store offset=4 + br 1 (;@11;) + end + local.get 4 + local.get 0 + i32.const 3 + i32.or + i32.store offset=4 + local.get 4 + local.get 0 + i32.add + local.tee 0 + local.get 0 + i32.load offset=4 + i32.const 1 + i32.or + i32.store offset=4 + i32.const 0 + local.set 7 + i32.const 0 + local.set 5 + end + i32.const 0 + local.get 5 + i32.store offset=67808 + i32.const 0 + local.get 7 + i32.store offset=67820 + local.get 4 + i32.const 8 + i32.add + local.set 0 + br 9 (;@1;) + end + block ;; label = @10 + i32.const 0 + i32.load offset=67812 + local.tee 7 + local.get 3 + i32.le_u + br_if 0 (;@10;) + i32.const 0 + local.get 7 + local.get 3 + i32.sub + local.tee 4 + i32.store offset=67812 + i32.const 0 + i32.const 0 + i32.load offset=67824 + local.tee 0 + local.get 3 + i32.add + local.tee 5 + i32.store offset=67824 + local.get 5 + local.get 4 + i32.const 1 + i32.or + i32.store offset=4 + local.get 0 + local.get 3 + i32.const 3 + i32.or + i32.store offset=4 + local.get 0 + i32.const 8 + i32.add + local.set 0 + br 9 (;@1;) + end + block ;; label = @10 + block ;; label = @11 + i32.const 0 + i32.load offset=68272 + i32.eqz + br_if 0 (;@11;) + i32.const 0 + i32.load offset=68280 + local.set 4 + br 1 (;@10;) + end + i32.const 0 + i64.const -1 + i64.store offset=68284 align=4 + i32.const 0 + i64.const 17592186048512 + i64.store offset=68276 align=4 + i32.const 0 + local.get 1 + i32.const 12 + i32.add + i32.const -16 + i32.and + i32.const 1431655768 + i32.xor + i32.store offset=68272 + i32.const 0 + i32.const 0 + i32.store offset=68292 + i32.const 0 + i32.const 0 + i32.store offset=68244 + i32.const 4096 + local.set 4 + end + i32.const 0 + local.set 0 + local.get 4 + local.get 3 + i32.const 47 + i32.add + local.tee 6 + i32.add + local.tee 2 + i32.const 0 + local.get 4 + i32.sub + local.tee 11 + i32.and + local.tee 8 + local.get 3 + i32.le_u + br_if 8 (;@1;) + i32.const 0 + local.set 0 + block ;; label = @10 + i32.const 0 + i32.load offset=68240 + local.tee 4 + i32.eqz + br_if 0 (;@10;) + i32.const 0 + i32.load offset=68232 + local.tee 5 + local.get 8 + i32.add + local.tee 10 + local.get 5 + i32.le_u + br_if 9 (;@1;) + local.get 10 + local.get 4 + i32.gt_u + br_if 9 (;@1;) + end + block ;; label = @10 + block ;; label = @11 + i32.const 0 + i32.load8_u offset=68244 + i32.const 4 + i32.and + br_if 0 (;@11;) + block ;; label = @12 + block ;; label = @13 + block ;; label = @14 + block ;; label = @15 + block ;; label = @16 + i32.const 0 + i32.load offset=67824 + local.tee 4 + i32.eqz + br_if 0 (;@16;) + i32.const 68248 + local.set 0 + loop ;; label = @17 + block ;; label = @18 + local.get 4 + local.get 0 + i32.load + local.tee 5 + i32.lt_u + br_if 0 (;@18;) + local.get 4 + local.get 5 + local.get 0 + i32.load offset=4 + i32.add + i32.lt_u + br_if 3 (;@15;) + end + local.get 0 + i32.load offset=8 + local.tee 0 + br_if 0 (;@17;) + end + end + i32.const 0 + call 9 + local.tee 7 + i32.const -1 + i32.eq + br_if 3 (;@12;) + local.get 8 + local.set 2 + block ;; label = @16 + i32.const 0 + i32.load offset=68276 + local.tee 0 + i32.const -1 + i32.add + local.tee 4 + local.get 7 + i32.and + i32.eqz + br_if 0 (;@16;) + local.get 8 + local.get 7 + i32.sub + local.get 4 + local.get 7 + i32.add + i32.const 0 + local.get 0 + i32.sub + i32.and + i32.add + local.set 2 + end + local.get 2 + local.get 3 + i32.le_u + br_if 3 (;@12;) + block ;; label = @16 + i32.const 0 + i32.load offset=68240 + local.tee 0 + i32.eqz + br_if 0 (;@16;) + i32.const 0 + i32.load offset=68232 + local.tee 4 + local.get 2 + i32.add + local.tee 5 + local.get 4 + i32.le_u + br_if 4 (;@12;) + local.get 5 + local.get 0 + i32.gt_u + br_if 4 (;@12;) + end + local.get 2 + call 9 + local.tee 0 + local.get 7 + i32.ne + br_if 1 (;@14;) + br 5 (;@10;) + end + local.get 2 + local.get 7 + i32.sub + local.get 11 + i32.and + local.tee 2 + call 9 + local.tee 7 + local.get 0 + i32.load + local.get 0 + i32.load offset=4 + i32.add + i32.eq + br_if 1 (;@13;) + local.get 7 + local.set 0 + end + local.get 0 + i32.const -1 + i32.eq + br_if 1 (;@12;) + block ;; label = @14 + local.get 2 + local.get 3 + i32.const 48 + i32.add + i32.lt_u + br_if 0 (;@14;) + local.get 0 + local.set 7 + br 4 (;@10;) + end + local.get 6 + local.get 2 + i32.sub + i32.const 0 + i32.load offset=68280 + local.tee 4 + i32.add + i32.const 0 + local.get 4 + i32.sub + i32.and + local.tee 4 + call 9 + i32.const -1 + i32.eq + br_if 1 (;@12;) + local.get 4 + local.get 2 + i32.add + local.set 2 + local.get 0 + local.set 7 + br 3 (;@10;) + end + local.get 7 + i32.const -1 + i32.ne + br_if 2 (;@10;) + end + i32.const 0 + i32.const 0 + i32.load offset=68244 + i32.const 4 + i32.or + i32.store offset=68244 + end + local.get 8 + call 9 + local.set 7 + i32.const 0 + call 9 + local.set 0 + local.get 7 + i32.const -1 + i32.eq + br_if 5 (;@5;) + local.get 0 + i32.const -1 + i32.eq + br_if 5 (;@5;) + local.get 7 + local.get 0 + i32.ge_u + br_if 5 (;@5;) + local.get 0 + local.get 7 + i32.sub + local.tee 2 + local.get 3 + i32.const 40 + i32.add + i32.le_u + br_if 5 (;@5;) + end + i32.const 0 + i32.const 0 + i32.load offset=68232 + local.get 2 + i32.add + local.tee 0 + i32.store offset=68232 + block ;; label = @10 + local.get 0 + i32.const 0 + i32.load offset=68236 + i32.le_u + br_if 0 (;@10;) + i32.const 0 + local.get 0 + i32.store offset=68236 + end + block ;; label = @10 + block ;; label = @11 + i32.const 0 + i32.load offset=67824 + local.tee 4 + i32.eqz + br_if 0 (;@11;) + i32.const 68248 + local.set 0 + loop ;; label = @12 + local.get 7 + local.get 0 + i32.load + local.tee 5 + local.get 0 + i32.load offset=4 + local.tee 8 + i32.add + i32.eq + br_if 2 (;@10;) + local.get 0 + i32.load offset=8 + local.tee 0 + br_if 0 (;@12;) + br 5 (;@7;) + end + unreachable + end + block ;; label = @11 + block ;; label = @12 + i32.const 0 + i32.load offset=67816 + local.tee 0 + i32.eqz + br_if 0 (;@12;) + local.get 7 + local.get 0 + i32.ge_u + br_if 1 (;@11;) + end + i32.const 0 + local.get 7 + i32.store offset=67816 + end + i32.const 0 + local.set 0 + i32.const 0 + local.get 2 + i32.store offset=68252 + i32.const 0 + local.get 7 + i32.store offset=68248 + i32.const 0 + i32.const -1 + i32.store offset=67832 + i32.const 0 + i32.const 0 + i32.load offset=68272 + i32.store offset=67836 + i32.const 0 + i32.const 0 + i32.store offset=68260 + loop ;; label = @11 + local.get 0 + i32.const 3 + i32.shl + local.tee 4 + i32.const 67848 + i32.add + local.get 4 + i32.const 67840 + i32.add + local.tee 5 + i32.store + local.get 4 + i32.const 67852 + i32.add + local.get 5 + i32.store + local.get 0 + i32.const 1 + i32.add + local.tee 0 + i32.const 32 + i32.ne + br_if 0 (;@11;) + end + i32.const 0 + local.get 2 + i32.const -40 + i32.add + local.tee 0 + i32.const -8 + local.get 7 + i32.sub + i32.const 7 + i32.and + local.tee 4 + i32.sub + local.tee 5 + i32.store offset=67812 + i32.const 0 + local.get 7 + local.get 4 + i32.add + local.tee 4 + i32.store offset=67824 + local.get 4 + local.get 5 + i32.const 1 + i32.or + i32.store offset=4 + local.get 7 + local.get 0 + i32.add + i32.const 40 + i32.store offset=4 + i32.const 0 + i32.const 0 + i32.load offset=68288 + i32.store offset=67828 + br 4 (;@6;) + end + local.get 4 + local.get 7 + i32.ge_u + br_if 2 (;@7;) + local.get 4 + local.get 5 + i32.lt_u + br_if 2 (;@7;) + local.get 0 + i32.load offset=12 + i32.const 8 + i32.and + br_if 2 (;@7;) + local.get 0 + local.get 8 + local.get 2 + i32.add + i32.store offset=4 + i32.const 0 + local.get 4 + i32.const -8 + local.get 4 + i32.sub + i32.const 7 + i32.and + local.tee 0 + i32.add + local.tee 5 + i32.store offset=67824 + i32.const 0 + i32.const 0 + i32.load offset=67812 + local.get 2 + i32.add + local.tee 7 + local.get 0 + i32.sub + local.tee 0 + i32.store offset=67812 + local.get 5 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 4 + local.get 7 + i32.add + i32.const 40 + i32.store offset=4 + i32.const 0 + i32.const 0 + i32.load offset=68288 + i32.store offset=67828 + br 3 (;@6;) + end + i32.const 0 + local.set 0 + br 6 (;@2;) + end + i32.const 0 + local.set 0 + br 4 (;@3;) + end + block ;; label = @7 + local.get 7 + i32.const 0 + i32.load offset=67816 + i32.ge_u + br_if 0 (;@7;) + i32.const 0 + local.get 7 + i32.store offset=67816 + end + local.get 7 + local.get 2 + i32.add + local.set 5 + i32.const 68248 + local.set 0 + block ;; label = @7 + block ;; label = @8 + loop ;; label = @9 + local.get 0 + i32.load + local.tee 8 + local.get 5 + i32.eq + br_if 1 (;@8;) + local.get 0 + i32.load offset=8 + local.tee 0 + br_if 0 (;@9;) + br 2 (;@7;) + end + unreachable + end + local.get 0 + i32.load8_u offset=12 + i32.const 8 + i32.and + i32.eqz + br_if 3 (;@4;) + end + i32.const 68248 + local.set 0 + block ;; label = @7 + loop ;; label = @8 + block ;; label = @9 + local.get 4 + local.get 0 + i32.load + local.tee 5 + i32.lt_u + br_if 0 (;@9;) + local.get 4 + local.get 5 + local.get 0 + i32.load offset=4 + i32.add + local.tee 5 + i32.lt_u + br_if 2 (;@7;) + end + local.get 0 + i32.load offset=8 + local.set 0 + br 0 (;@8;) + end + unreachable + end + i32.const 0 + local.get 2 + i32.const -40 + i32.add + local.tee 0 + i32.const -8 + local.get 7 + i32.sub + i32.const 7 + i32.and + local.tee 8 + i32.sub + local.tee 11 + i32.store offset=67812 + i32.const 0 + local.get 7 + local.get 8 + i32.add + local.tee 8 + i32.store offset=67824 + local.get 8 + local.get 11 + i32.const 1 + i32.or + i32.store offset=4 + local.get 7 + local.get 0 + i32.add + i32.const 40 + i32.store offset=4 + i32.const 0 + i32.const 0 + i32.load offset=68288 + i32.store offset=67828 + local.get 4 + local.get 5 + i32.const 39 + local.get 5 + i32.sub + i32.const 7 + i32.and + i32.add + i32.const -47 + i32.add + local.tee 0 + local.get 0 + local.get 4 + i32.const 16 + i32.add + i32.lt_u + select + local.tee 8 + i32.const 27 + i32.store offset=4 + local.get 8 + i32.const 16 + i32.add + i32.const 0 + i64.load offset=68256 align=4 + i64.store align=4 + local.get 8 + i32.const 0 + i64.load offset=68248 align=4 + i64.store offset=8 align=4 + i32.const 0 + local.get 8 + i32.const 8 + i32.add + i32.store offset=68256 + i32.const 0 + local.get 2 + i32.store offset=68252 + i32.const 0 + local.get 7 + i32.store offset=68248 + i32.const 0 + i32.const 0 + i32.store offset=68260 + local.get 8 + i32.const 24 + i32.add + local.set 0 + loop ;; label = @7 + local.get 0 + i32.const 7 + i32.store offset=4 + local.get 0 + i32.const 8 + i32.add + local.set 7 + local.get 0 + i32.const 4 + i32.add + local.set 0 + local.get 7 + local.get 5 + i32.lt_u + br_if 0 (;@7;) + end + local.get 8 + local.get 4 + i32.eq + br_if 0 (;@6;) + local.get 8 + local.get 8 + i32.load offset=4 + i32.const -2 + i32.and + i32.store offset=4 + local.get 4 + local.get 8 + local.get 4 + i32.sub + local.tee 7 + i32.const 1 + i32.or + i32.store offset=4 + local.get 8 + local.get 7 + i32.store + block ;; label = @7 + block ;; label = @8 + local.get 7 + i32.const 255 + i32.gt_u + br_if 0 (;@8;) + local.get 7 + i32.const -8 + i32.and + i32.const 67840 + i32.add + local.set 0 + block ;; label = @9 + block ;; label = @10 + i32.const 0 + i32.load offset=67800 + local.tee 5 + i32.const 1 + local.get 7 + i32.const 3 + i32.shr_u + i32.shl + local.tee 7 + i32.and + br_if 0 (;@10;) + i32.const 0 + local.get 5 + local.get 7 + i32.or + i32.store offset=67800 + local.get 0 + local.set 5 + br 1 (;@9;) + end + local.get 0 + i32.load offset=8 + local.set 5 + end + local.get 0 + local.get 4 + i32.store offset=8 + local.get 5 + local.get 4 + i32.store offset=12 + i32.const 12 + local.set 7 + i32.const 8 + local.set 8 + br 1 (;@7;) + end + i32.const 31 + local.set 0 + block ;; label = @8 + local.get 7 + i32.const 16777215 + i32.gt_u + br_if 0 (;@8;) + local.get 7 + i32.const 38 + local.get 7 + i32.const 8 + i32.shr_u + i32.clz + local.tee 0 + i32.sub + i32.shr_u + i32.const 1 + i32.and + local.get 0 + i32.const 1 + i32.shl + i32.sub + i32.const 62 + i32.add + local.set 0 + end + local.get 4 + local.get 0 + i32.store offset=28 + local.get 4 + i64.const 0 + i64.store offset=16 align=4 + local.get 0 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + local.set 5 + block ;; label = @8 + block ;; label = @9 + block ;; label = @10 + i32.const 0 + i32.load offset=67804 + local.tee 8 + i32.const 1 + local.get 0 + i32.shl + local.tee 2 + i32.and + br_if 0 (;@10;) + i32.const 0 + local.get 8 + local.get 2 + i32.or + i32.store offset=67804 + local.get 5 + local.get 4 + i32.store + local.get 4 + local.get 5 + i32.store offset=24 + br 1 (;@9;) + end + local.get 7 + i32.const 0 + i32.const 25 + local.get 0 + i32.const 1 + i32.shr_u + i32.sub + local.get 0 + i32.const 31 + i32.eq + select + i32.shl + local.set 0 + local.get 5 + i32.load + local.set 8 + loop ;; label = @10 + local.get 8 + local.tee 5 + i32.load offset=4 + i32.const -8 + i32.and + local.get 7 + i32.eq + br_if 2 (;@8;) + local.get 0 + i32.const 29 + i32.shr_u + local.set 8 + local.get 0 + i32.const 1 + i32.shl + local.set 0 + local.get 5 + local.get 8 + i32.const 4 + i32.and + i32.add + local.tee 2 + i32.load offset=16 + local.tee 8 + br_if 0 (;@10;) + end + local.get 2 + i32.const 16 + i32.add + local.get 4 + i32.store + local.get 4 + local.get 5 + i32.store offset=24 + end + i32.const 8 + local.set 7 + i32.const 12 + local.set 8 + local.get 4 + local.set 5 + local.get 4 + local.set 0 + br 1 (;@7;) + end + local.get 5 + i32.load offset=8 + local.tee 0 + local.get 4 + i32.store offset=12 + local.get 5 + local.get 4 + i32.store offset=8 + local.get 4 + local.get 0 + i32.store offset=8 + i32.const 0 + local.set 0 + i32.const 24 + local.set 7 + i32.const 12 + local.set 8 + end + local.get 4 + local.get 8 + i32.add + local.get 5 + i32.store + local.get 4 + local.get 7 + i32.add + local.get 0 + i32.store + end + i32.const 0 + i32.load offset=67812 + local.tee 0 + local.get 3 + i32.le_u + br_if 0 (;@5;) + i32.const 0 + local.get 0 + local.get 3 + i32.sub + local.tee 4 + i32.store offset=67812 + i32.const 0 + i32.const 0 + i32.load offset=67824 + local.tee 0 + local.get 3 + i32.add + local.tee 5 + i32.store offset=67824 + local.get 5 + local.get 4 + i32.const 1 + i32.or + i32.store offset=4 + local.get 0 + local.get 3 + i32.const 3 + i32.or + i32.store offset=4 + local.get 0 + i32.const 8 + i32.add + local.set 0 + br 4 (;@1;) + end + call 3 + i32.const 48 + i32.store + i32.const 0 + local.set 0 + br 3 (;@1;) + end + local.get 0 + local.get 7 + i32.store + local.get 0 + local.get 0 + i32.load offset=4 + local.get 2 + i32.add + i32.store offset=4 + local.get 7 + local.get 8 + local.get 3 + call 5 + local.set 0 + br 2 (;@1;) + end + block ;; label = @3 + local.get 11 + i32.eqz + br_if 0 (;@3;) + block ;; label = @4 + block ;; label = @5 + local.get 8 + local.get 8 + i32.load offset=28 + local.tee 7 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + local.tee 5 + i32.load + i32.ne + br_if 0 (;@5;) + local.get 5 + local.get 0 + i32.store + local.get 0 + br_if 1 (;@4;) + i32.const 0 + local.get 10 + i32.const -2 + local.get 7 + i32.rotl + i32.and + local.tee 10 + i32.store offset=67804 + br 2 (;@3;) + end + block ;; label = @5 + block ;; label = @6 + local.get 11 + i32.load offset=16 + local.get 8 + i32.ne + br_if 0 (;@6;) + local.get 11 + local.get 0 + i32.store offset=16 + br 1 (;@5;) + end + local.get 11 + local.get 0 + i32.store offset=20 + end + local.get 0 + i32.eqz + br_if 1 (;@3;) + end + local.get 0 + local.get 11 + i32.store offset=24 + block ;; label = @4 + local.get 8 + i32.load offset=16 + local.tee 5 + i32.eqz + br_if 0 (;@4;) + local.get 0 + local.get 5 + i32.store offset=16 + local.get 5 + local.get 0 + i32.store offset=24 + end + local.get 8 + i32.load offset=20 + local.tee 5 + i32.eqz + br_if 0 (;@3;) + local.get 0 + local.get 5 + i32.store offset=20 + local.get 5 + local.get 0 + i32.store offset=24 + end + block ;; label = @3 + block ;; label = @4 + local.get 4 + i32.const 15 + i32.gt_u + br_if 0 (;@4;) + local.get 8 + local.get 4 + local.get 3 + i32.add + local.tee 0 + i32.const 3 + i32.or + i32.store offset=4 + local.get 8 + local.get 0 + i32.add + local.tee 0 + local.get 0 + i32.load offset=4 + i32.const 1 + i32.or + i32.store offset=4 + br 1 (;@3;) + end + local.get 8 + local.get 3 + i32.const 3 + i32.or + i32.store offset=4 + local.get 8 + local.get 3 + i32.add + local.tee 7 + local.get 4 + i32.const 1 + i32.or + i32.store offset=4 + local.get 7 + local.get 4 + i32.add + local.get 4 + i32.store + block ;; label = @4 + local.get 4 + i32.const 255 + i32.gt_u + br_if 0 (;@4;) + local.get 4 + i32.const -8 + i32.and + i32.const 67840 + i32.add + local.set 0 + block ;; label = @5 + block ;; label = @6 + i32.const 0 + i32.load offset=67800 + local.tee 3 + i32.const 1 + local.get 4 + i32.const 3 + i32.shr_u + i32.shl + local.tee 4 + i32.and + br_if 0 (;@6;) + i32.const 0 + local.get 3 + local.get 4 + i32.or + i32.store offset=67800 + local.get 0 + local.set 4 + br 1 (;@5;) + end + local.get 0 + i32.load offset=8 + local.set 4 + end + local.get 0 + local.get 7 + i32.store offset=8 + local.get 4 + local.get 7 + i32.store offset=12 + local.get 7 + local.get 0 + i32.store offset=12 + local.get 7 + local.get 4 + i32.store offset=8 + br 1 (;@3;) + end + i32.const 31 + local.set 0 + block ;; label = @4 + local.get 4 + i32.const 16777215 + i32.gt_u + br_if 0 (;@4;) + local.get 4 + i32.const 38 + local.get 4 + i32.const 8 + i32.shr_u + i32.clz + local.tee 0 + i32.sub + i32.shr_u + i32.const 1 + i32.and + local.get 0 + i32.const 1 + i32.shl + i32.sub + i32.const 62 + i32.add + local.set 0 + end + local.get 7 + local.get 0 + i32.store offset=28 + local.get 7 + i64.const 0 + i64.store offset=16 align=4 + local.get 0 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + local.set 3 + block ;; label = @4 + block ;; label = @5 + block ;; label = @6 + local.get 10 + i32.const 1 + local.get 0 + i32.shl + local.tee 5 + i32.and + br_if 0 (;@6;) + i32.const 0 + local.get 10 + local.get 5 + i32.or + i32.store offset=67804 + local.get 3 + local.get 7 + i32.store + local.get 7 + local.get 3 + i32.store offset=24 + br 1 (;@5;) + end + local.get 4 + i32.const 0 + i32.const 25 + local.get 0 + i32.const 1 + i32.shr_u + i32.sub + local.get 0 + i32.const 31 + i32.eq + select + i32.shl + local.set 0 + local.get 3 + i32.load + local.set 5 + loop ;; label = @6 + local.get 5 + local.tee 3 + i32.load offset=4 + i32.const -8 + i32.and + local.get 4 + i32.eq + br_if 2 (;@4;) + local.get 0 + i32.const 29 + i32.shr_u + local.set 5 + local.get 0 + i32.const 1 + i32.shl + local.set 0 + local.get 3 + local.get 5 + i32.const 4 + i32.and + i32.add + local.tee 2 + i32.load offset=16 + local.tee 5 + br_if 0 (;@6;) + end + local.get 2 + i32.const 16 + i32.add + local.get 7 + i32.store + local.get 7 + local.get 3 + i32.store offset=24 + end + local.get 7 + local.get 7 + i32.store offset=12 + local.get 7 + local.get 7 + i32.store offset=8 + br 1 (;@3;) + end + local.get 3 + i32.load offset=8 + local.tee 0 + local.get 7 + i32.store offset=12 + local.get 3 + local.get 7 + i32.store offset=8 + local.get 7 + i32.const 0 + i32.store offset=24 + local.get 7 + local.get 3 + i32.store offset=12 + local.get 7 + local.get 0 + i32.store offset=8 + end + local.get 8 + i32.const 8 + i32.add + local.set 0 + br 1 (;@1;) + end + block ;; label = @2 + local.get 10 + i32.eqz + br_if 0 (;@2;) + block ;; label = @3 + block ;; label = @4 + local.get 7 + local.get 7 + i32.load offset=28 + local.tee 8 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + local.tee 5 + i32.load + i32.ne + br_if 0 (;@4;) + local.get 5 + local.get 0 + i32.store + local.get 0 + br_if 1 (;@3;) + i32.const 0 + local.get 9 + i32.const -2 + local.get 8 + i32.rotl + i32.and + i32.store offset=67804 + br 2 (;@2;) + end + block ;; label = @4 + block ;; label = @5 + local.get 10 + i32.load offset=16 + local.get 7 + i32.ne + br_if 0 (;@5;) + local.get 10 + local.get 0 + i32.store offset=16 + br 1 (;@4;) + end + local.get 10 + local.get 0 + i32.store offset=20 + end + local.get 0 + i32.eqz + br_if 1 (;@2;) + end + local.get 0 + local.get 10 + i32.store offset=24 + block ;; label = @3 + local.get 7 + i32.load offset=16 + local.tee 5 + i32.eqz + br_if 0 (;@3;) + local.get 0 + local.get 5 + i32.store offset=16 + local.get 5 + local.get 0 + i32.store offset=24 + end + local.get 7 + i32.load offset=20 + local.tee 5 + i32.eqz + br_if 0 (;@2;) + local.get 0 + local.get 5 + i32.store offset=20 + local.get 5 + local.get 0 + i32.store offset=24 + end + block ;; label = @2 + block ;; label = @3 + local.get 4 + i32.const 15 + i32.gt_u + br_if 0 (;@3;) + local.get 7 + local.get 4 + local.get 3 + i32.add + local.tee 0 + i32.const 3 + i32.or + i32.store offset=4 + local.get 7 + local.get 0 + i32.add + local.tee 0 + local.get 0 + i32.load offset=4 + i32.const 1 + i32.or + i32.store offset=4 + br 1 (;@2;) + end + local.get 7 + local.get 3 + i32.const 3 + i32.or + i32.store offset=4 + local.get 7 + local.get 3 + i32.add + local.tee 3 + local.get 4 + i32.const 1 + i32.or + i32.store offset=4 + local.get 3 + local.get 4 + i32.add + local.get 4 + i32.store + block ;; label = @3 + local.get 6 + i32.eqz + br_if 0 (;@3;) + local.get 6 + i32.const -8 + i32.and + i32.const 67840 + i32.add + local.set 5 + i32.const 0 + i32.load offset=67820 + local.set 0 + block ;; label = @4 + block ;; label = @5 + i32.const 1 + local.get 6 + i32.const 3 + i32.shr_u + i32.shl + local.tee 8 + local.get 2 + i32.and + br_if 0 (;@5;) + i32.const 0 + local.get 8 + local.get 2 + i32.or + i32.store offset=67800 + local.get 5 + local.set 8 + br 1 (;@4;) + end + local.get 5 + i32.load offset=8 + local.set 8 + end + local.get 5 + local.get 0 + i32.store offset=8 + local.get 8 + local.get 0 + i32.store offset=12 + local.get 0 + local.get 5 + i32.store offset=12 + local.get 0 + local.get 8 + i32.store offset=8 + end + i32.const 0 + local.get 3 + i32.store offset=67820 + i32.const 0 + local.get 4 + i32.store offset=67808 + end + local.get 7 + i32.const 8 + i32.add + local.set 0 + end + local.get 1 + i32.const 16 + i32.add + global.set 0 + local.get 0) + (func (;5;) (type 5) (param i32 i32 i32) (result i32) + (local i32 i32 i32 i32 i32 i32 i32) + local.get 0 + i32.const -8 + local.get 0 + i32.sub + i32.const 7 + i32.and + i32.add + local.tee 3 + local.get 2 + i32.const 3 + i32.or + i32.store offset=4 + local.get 1 + i32.const -8 + local.get 1 + i32.sub + i32.const 7 + i32.and + i32.add + local.tee 4 + local.get 3 + local.get 2 + i32.add + local.tee 5 + i32.sub + local.set 0 + block ;; label = @1 + block ;; label = @2 + local.get 4 + i32.const 0 + i32.load offset=67824 + i32.ne + br_if 0 (;@2;) + i32.const 0 + local.get 5 + i32.store offset=67824 + i32.const 0 + i32.const 0 + i32.load offset=67812 + local.get 0 + i32.add + local.tee 2 + i32.store offset=67812 + local.get 5 + local.get 2 + i32.const 1 + i32.or + i32.store offset=4 + br 1 (;@1;) + end + block ;; label = @2 + local.get 4 + i32.const 0 + i32.load offset=67820 + i32.ne + br_if 0 (;@2;) + i32.const 0 + local.get 5 + i32.store offset=67820 + i32.const 0 + i32.const 0 + i32.load offset=67808 + local.get 0 + i32.add + local.tee 2 + i32.store offset=67808 + local.get 5 + local.get 2 + i32.const 1 + i32.or + i32.store offset=4 + local.get 5 + local.get 2 + i32.add + local.get 2 + i32.store + br 1 (;@1;) + end + block ;; label = @2 + local.get 4 + i32.load offset=4 + local.tee 1 + i32.const 3 + i32.and + i32.const 1 + i32.ne + br_if 0 (;@2;) + local.get 1 + i32.const -8 + i32.and + local.set 6 + local.get 4 + i32.load offset=12 + local.set 2 + block ;; label = @3 + block ;; label = @4 + local.get 1 + i32.const 255 + i32.gt_u + br_if 0 (;@4;) + block ;; label = @5 + local.get 2 + local.get 4 + i32.load offset=8 + local.tee 7 + i32.ne + br_if 0 (;@5;) + i32.const 0 + i32.const 0 + i32.load offset=67800 + i32.const -2 + local.get 1 + i32.const 3 + i32.shr_u + i32.rotl + i32.and + i32.store offset=67800 + br 2 (;@3;) + end + local.get 7 + local.get 2 + i32.store offset=12 + local.get 2 + local.get 7 + i32.store offset=8 + br 1 (;@3;) + end + local.get 4 + i32.load offset=24 + local.set 8 + block ;; label = @4 + block ;; label = @5 + local.get 2 + local.get 4 + i32.eq + br_if 0 (;@5;) + local.get 4 + i32.load offset=8 + local.tee 1 + local.get 2 + i32.store offset=12 + local.get 2 + local.get 1 + i32.store offset=8 + br 1 (;@4;) + end + block ;; label = @5 + block ;; label = @6 + block ;; label = @7 + local.get 4 + i32.load offset=20 + local.tee 1 + i32.eqz + br_if 0 (;@7;) + local.get 4 + i32.const 20 + i32.add + local.set 7 + br 1 (;@6;) + end + local.get 4 + i32.load offset=16 + local.tee 1 + i32.eqz + br_if 1 (;@5;) + local.get 4 + i32.const 16 + i32.add + local.set 7 + end + loop ;; label = @6 + local.get 7 + local.set 9 + local.get 1 + local.tee 2 + i32.const 20 + i32.add + local.set 7 + local.get 2 + i32.load offset=20 + local.tee 1 + br_if 0 (;@6;) + local.get 2 + i32.const 16 + i32.add + local.set 7 + local.get 2 + i32.load offset=16 + local.tee 1 + br_if 0 (;@6;) + end + local.get 9 + i32.const 0 + i32.store + br 1 (;@4;) + end + i32.const 0 + local.set 2 + end + local.get 8 + i32.eqz + br_if 0 (;@3;) + block ;; label = @4 + block ;; label = @5 + local.get 4 + local.get 4 + i32.load offset=28 + local.tee 7 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + local.tee 1 + i32.load + i32.ne + br_if 0 (;@5;) + local.get 1 + local.get 2 + i32.store + local.get 2 + br_if 1 (;@4;) + i32.const 0 + i32.const 0 + i32.load offset=67804 + i32.const -2 + local.get 7 + i32.rotl + i32.and + i32.store offset=67804 + br 2 (;@3;) + end + block ;; label = @5 + block ;; label = @6 + local.get 8 + i32.load offset=16 + local.get 4 + i32.ne + br_if 0 (;@6;) + local.get 8 + local.get 2 + i32.store offset=16 + br 1 (;@5;) + end + local.get 8 + local.get 2 + i32.store offset=20 + end + local.get 2 + i32.eqz + br_if 1 (;@3;) + end + local.get 2 + local.get 8 + i32.store offset=24 + block ;; label = @4 + local.get 4 + i32.load offset=16 + local.tee 1 + i32.eqz + br_if 0 (;@4;) + local.get 2 + local.get 1 + i32.store offset=16 + local.get 1 + local.get 2 + i32.store offset=24 + end + local.get 4 + i32.load offset=20 + local.tee 1 + i32.eqz + br_if 0 (;@3;) + local.get 2 + local.get 1 + i32.store offset=20 + local.get 1 + local.get 2 + i32.store offset=24 + end + local.get 6 + local.get 0 + i32.add + local.set 0 + local.get 4 + local.get 6 + i32.add + local.tee 4 + i32.load offset=4 + local.set 1 + end + local.get 4 + local.get 1 + i32.const -2 + i32.and + i32.store offset=4 + local.get 5 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 5 + local.get 0 + i32.add + local.get 0 + i32.store + block ;; label = @2 + local.get 0 + i32.const 255 + i32.gt_u + br_if 0 (;@2;) + local.get 0 + i32.const -8 + i32.and + i32.const 67840 + i32.add + local.set 2 + block ;; label = @3 + block ;; label = @4 + i32.const 0 + i32.load offset=67800 + local.tee 1 + i32.const 1 + local.get 0 + i32.const 3 + i32.shr_u + i32.shl + local.tee 0 + i32.and + br_if 0 (;@4;) + i32.const 0 + local.get 1 + local.get 0 + i32.or + i32.store offset=67800 + local.get 2 + local.set 0 + br 1 (;@3;) + end + local.get 2 + i32.load offset=8 + local.set 0 + end + local.get 2 + local.get 5 + i32.store offset=8 + local.get 0 + local.get 5 + i32.store offset=12 + local.get 5 + local.get 2 + i32.store offset=12 + local.get 5 + local.get 0 + i32.store offset=8 + br 1 (;@1;) + end + i32.const 31 + local.set 2 + block ;; label = @2 + local.get 0 + i32.const 16777215 + i32.gt_u + br_if 0 (;@2;) + local.get 0 + i32.const 38 + local.get 0 + i32.const 8 + i32.shr_u + i32.clz + local.tee 2 + i32.sub + i32.shr_u + i32.const 1 + i32.and + local.get 2 + i32.const 1 + i32.shl + i32.sub + i32.const 62 + i32.add + local.set 2 + end + local.get 5 + local.get 2 + i32.store offset=28 + local.get 5 + i64.const 0 + i64.store offset=16 align=4 + local.get 2 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + local.set 1 + block ;; label = @2 + block ;; label = @3 + block ;; label = @4 + i32.const 0 + i32.load offset=67804 + local.tee 7 + i32.const 1 + local.get 2 + i32.shl + local.tee 4 + i32.and + br_if 0 (;@4;) + i32.const 0 + local.get 7 + local.get 4 + i32.or + i32.store offset=67804 + local.get 1 + local.get 5 + i32.store + local.get 5 + local.get 1 + i32.store offset=24 + br 1 (;@3;) + end + local.get 0 + i32.const 0 + i32.const 25 + local.get 2 + i32.const 1 + i32.shr_u + i32.sub + local.get 2 + i32.const 31 + i32.eq + select + i32.shl + local.set 2 + local.get 1 + i32.load + local.set 7 + loop ;; label = @4 + local.get 7 + local.tee 1 + i32.load offset=4 + i32.const -8 + i32.and + local.get 0 + i32.eq + br_if 2 (;@2;) + local.get 2 + i32.const 29 + i32.shr_u + local.set 7 + local.get 2 + i32.const 1 + i32.shl + local.set 2 + local.get 1 + local.get 7 + i32.const 4 + i32.and + i32.add + local.tee 4 + i32.load offset=16 + local.tee 7 + br_if 0 (;@4;) + end + local.get 4 + i32.const 16 + i32.add + local.get 5 + i32.store + local.get 5 + local.get 1 + i32.store offset=24 + end + local.get 5 + local.get 5 + i32.store offset=12 + local.get 5 + local.get 5 + i32.store offset=8 + br 1 (;@1;) + end + local.get 1 + i32.load offset=8 + local.tee 2 + local.get 5 + i32.store offset=12 + local.get 1 + local.get 5 + i32.store offset=8 + local.get 5 + i32.const 0 + i32.store offset=24 + local.get 5 + local.get 1 + i32.store offset=12 + local.get 5 + local.get 2 + i32.store offset=8 + end + local.get 3 + i32.const 8 + i32.add) + (func (;6;) (type 3) (param i32) + (local i32 i32 i32 i32 i32 i32 i32 i32) + block ;; label = @1 + local.get 0 + i32.eqz + br_if 0 (;@1;) + local.get 0 + i32.const -8 + i32.add + local.tee 1 + local.get 0 + i32.const -4 + i32.add + i32.load + local.tee 2 + i32.const -8 + i32.and + local.tee 0 + i32.add + local.set 3 + block ;; label = @2 + local.get 2 + i32.const 1 + i32.and + br_if 0 (;@2;) + local.get 2 + i32.const 2 + i32.and + i32.eqz + br_if 1 (;@1;) + local.get 1 + local.get 1 + i32.load + local.tee 4 + i32.sub + local.tee 1 + i32.const 0 + i32.load offset=67816 + i32.lt_u + br_if 1 (;@1;) + local.get 4 + local.get 0 + i32.add + local.set 0 + block ;; label = @3 + block ;; label = @4 + block ;; label = @5 + block ;; label = @6 + local.get 1 + i32.const 0 + i32.load offset=67820 + i32.eq + br_if 0 (;@6;) + local.get 1 + i32.load offset=12 + local.set 2 + block ;; label = @7 + local.get 4 + i32.const 255 + i32.gt_u + br_if 0 (;@7;) + local.get 2 + local.get 1 + i32.load offset=8 + local.tee 5 + i32.ne + br_if 2 (;@5;) + i32.const 0 + i32.const 0 + i32.load offset=67800 + i32.const -2 + local.get 4 + i32.const 3 + i32.shr_u + i32.rotl + i32.and + i32.store offset=67800 + br 5 (;@2;) + end + local.get 1 + i32.load offset=24 + local.set 6 + block ;; label = @7 + local.get 2 + local.get 1 + i32.eq + br_if 0 (;@7;) + local.get 1 + i32.load offset=8 + local.tee 4 + local.get 2 + i32.store offset=12 + local.get 2 + local.get 4 + i32.store offset=8 + br 4 (;@3;) + end + block ;; label = @7 + block ;; label = @8 + local.get 1 + i32.load offset=20 + local.tee 4 + i32.eqz + br_if 0 (;@8;) + local.get 1 + i32.const 20 + i32.add + local.set 5 + br 1 (;@7;) + end + local.get 1 + i32.load offset=16 + local.tee 4 + i32.eqz + br_if 3 (;@4;) + local.get 1 + i32.const 16 + i32.add + local.set 5 + end + loop ;; label = @7 + local.get 5 + local.set 7 + local.get 4 + local.tee 2 + i32.const 20 + i32.add + local.set 5 + local.get 2 + i32.load offset=20 + local.tee 4 + br_if 0 (;@7;) + local.get 2 + i32.const 16 + i32.add + local.set 5 + local.get 2 + i32.load offset=16 + local.tee 4 + br_if 0 (;@7;) + end + local.get 7 + i32.const 0 + i32.store + br 3 (;@3;) + end + local.get 3 + i32.load offset=4 + local.tee 2 + i32.const 3 + i32.and + i32.const 3 + i32.ne + br_if 3 (;@2;) + i32.const 0 + local.get 0 + i32.store offset=67808 + local.get 3 + local.get 2 + i32.const -2 + i32.and + i32.store offset=4 + local.get 1 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 3 + local.get 0 + i32.store + return + end + local.get 5 + local.get 2 + i32.store offset=12 + local.get 2 + local.get 5 + i32.store offset=8 + br 2 (;@2;) + end + i32.const 0 + local.set 2 + end + local.get 6 + i32.eqz + br_if 0 (;@2;) + block ;; label = @3 + block ;; label = @4 + local.get 1 + local.get 1 + i32.load offset=28 + local.tee 5 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + local.tee 4 + i32.load + i32.ne + br_if 0 (;@4;) + local.get 4 + local.get 2 + i32.store + local.get 2 + br_if 1 (;@3;) + i32.const 0 + i32.const 0 + i32.load offset=67804 + i32.const -2 + local.get 5 + i32.rotl + i32.and + i32.store offset=67804 + br 2 (;@2;) + end + block ;; label = @4 + block ;; label = @5 + local.get 6 + i32.load offset=16 + local.get 1 + i32.ne + br_if 0 (;@5;) + local.get 6 + local.get 2 + i32.store offset=16 + br 1 (;@4;) + end + local.get 6 + local.get 2 + i32.store offset=20 + end + local.get 2 + i32.eqz + br_if 1 (;@2;) + end + local.get 2 + local.get 6 + i32.store offset=24 + block ;; label = @3 + local.get 1 + i32.load offset=16 + local.tee 4 + i32.eqz + br_if 0 (;@3;) + local.get 2 + local.get 4 + i32.store offset=16 + local.get 4 + local.get 2 + i32.store offset=24 + end + local.get 1 + i32.load offset=20 + local.tee 4 + i32.eqz + br_if 0 (;@2;) + local.get 2 + local.get 4 + i32.store offset=20 + local.get 4 + local.get 2 + i32.store offset=24 + end + local.get 1 + local.get 3 + i32.ge_u + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.tee 4 + i32.const 1 + i32.and + i32.eqz + br_if 0 (;@1;) + block ;; label = @2 + block ;; label = @3 + block ;; label = @4 + block ;; label = @5 + block ;; label = @6 + local.get 4 + i32.const 2 + i32.and + br_if 0 (;@6;) + block ;; label = @7 + local.get 3 + i32.const 0 + i32.load offset=67824 + i32.ne + br_if 0 (;@7;) + i32.const 0 + local.get 1 + i32.store offset=67824 + i32.const 0 + i32.const 0 + i32.load offset=67812 + local.get 0 + i32.add + local.tee 0 + i32.store offset=67812 + local.get 1 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 1 + i32.const 0 + i32.load offset=67820 + i32.ne + br_if 6 (;@1;) + i32.const 0 + i32.const 0 + i32.store offset=67808 + i32.const 0 + i32.const 0 + i32.store offset=67820 + return + end + block ;; label = @7 + local.get 3 + i32.const 0 + i32.load offset=67820 + local.tee 6 + i32.ne + br_if 0 (;@7;) + i32.const 0 + local.get 1 + i32.store offset=67820 + i32.const 0 + i32.const 0 + i32.load offset=67808 + local.get 0 + i32.add + local.tee 0 + i32.store offset=67808 + local.get 1 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 1 + local.get 0 + i32.add + local.get 0 + i32.store + return + end + local.get 4 + i32.const -8 + i32.and + local.get 0 + i32.add + local.set 0 + local.get 3 + i32.load offset=12 + local.set 2 + block ;; label = @7 + local.get 4 + i32.const 255 + i32.gt_u + br_if 0 (;@7;) + block ;; label = @8 + local.get 2 + local.get 3 + i32.load offset=8 + local.tee 5 + i32.ne + br_if 0 (;@8;) + i32.const 0 + i32.const 0 + i32.load offset=67800 + i32.const -2 + local.get 4 + i32.const 3 + i32.shr_u + i32.rotl + i32.and + i32.store offset=67800 + br 5 (;@3;) + end + local.get 5 + local.get 2 + i32.store offset=12 + local.get 2 + local.get 5 + i32.store offset=8 + br 4 (;@3;) + end + local.get 3 + i32.load offset=24 + local.set 8 + block ;; label = @7 + local.get 2 + local.get 3 + i32.eq + br_if 0 (;@7;) + local.get 3 + i32.load offset=8 + local.tee 4 + local.get 2 + i32.store offset=12 + local.get 2 + local.get 4 + i32.store offset=8 + br 3 (;@4;) + end + block ;; label = @7 + block ;; label = @8 + local.get 3 + i32.load offset=20 + local.tee 4 + i32.eqz + br_if 0 (;@8;) + local.get 3 + i32.const 20 + i32.add + local.set 5 + br 1 (;@7;) + end + local.get 3 + i32.load offset=16 + local.tee 4 + i32.eqz + br_if 2 (;@5;) + local.get 3 + i32.const 16 + i32.add + local.set 5 + end + loop ;; label = @7 + local.get 5 + local.set 7 + local.get 4 + local.tee 2 + i32.const 20 + i32.add + local.set 5 + local.get 2 + i32.load offset=20 + local.tee 4 + br_if 0 (;@7;) + local.get 2 + i32.const 16 + i32.add + local.set 5 + local.get 2 + i32.load offset=16 + local.tee 4 + br_if 0 (;@7;) + end + local.get 7 + i32.const 0 + i32.store + br 2 (;@4;) + end + local.get 3 + local.get 4 + i32.const -2 + i32.and + i32.store offset=4 + local.get 1 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 1 + local.get 0 + i32.add + local.get 0 + i32.store + br 3 (;@2;) + end + i32.const 0 + local.set 2 + end + local.get 8 + i32.eqz + br_if 0 (;@3;) + block ;; label = @4 + block ;; label = @5 + local.get 3 + local.get 3 + i32.load offset=28 + local.tee 5 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + local.tee 4 + i32.load + i32.ne + br_if 0 (;@5;) + local.get 4 + local.get 2 + i32.store + local.get 2 + br_if 1 (;@4;) + i32.const 0 + i32.const 0 + i32.load offset=67804 + i32.const -2 + local.get 5 + i32.rotl + i32.and + i32.store offset=67804 + br 2 (;@3;) + end + block ;; label = @5 + block ;; label = @6 + local.get 8 + i32.load offset=16 + local.get 3 + i32.ne + br_if 0 (;@6;) + local.get 8 + local.get 2 + i32.store offset=16 + br 1 (;@5;) + end + local.get 8 + local.get 2 + i32.store offset=20 + end + local.get 2 + i32.eqz + br_if 1 (;@3;) + end + local.get 2 + local.get 8 + i32.store offset=24 + block ;; label = @4 + local.get 3 + i32.load offset=16 + local.tee 4 + i32.eqz + br_if 0 (;@4;) + local.get 2 + local.get 4 + i32.store offset=16 + local.get 4 + local.get 2 + i32.store offset=24 + end + local.get 3 + i32.load offset=20 + local.tee 4 + i32.eqz + br_if 0 (;@3;) + local.get 2 + local.get 4 + i32.store offset=20 + local.get 4 + local.get 2 + i32.store offset=24 + end + local.get 1 + local.get 0 + i32.const 1 + i32.or + i32.store offset=4 + local.get 1 + local.get 0 + i32.add + local.get 0 + i32.store + local.get 1 + local.get 6 + i32.ne + br_if 0 (;@2;) + i32.const 0 + local.get 0 + i32.store offset=67808 + return + end + block ;; label = @2 + local.get 0 + i32.const 255 + i32.gt_u + br_if 0 (;@2;) + local.get 0 + i32.const -8 + i32.and + i32.const 67840 + i32.add + local.set 2 + block ;; label = @3 + block ;; label = @4 + i32.const 0 + i32.load offset=67800 + local.tee 4 + i32.const 1 + local.get 0 + i32.const 3 + i32.shr_u + i32.shl + local.tee 0 + i32.and + br_if 0 (;@4;) + i32.const 0 + local.get 4 + local.get 0 + i32.or + i32.store offset=67800 + local.get 2 + local.set 0 + br 1 (;@3;) + end + local.get 2 + i32.load offset=8 + local.set 0 + end + local.get 2 + local.get 1 + i32.store offset=8 + local.get 0 + local.get 1 + i32.store offset=12 + local.get 1 + local.get 2 + i32.store offset=12 + local.get 1 + local.get 0 + i32.store offset=8 + return + end + i32.const 31 + local.set 2 + block ;; label = @2 + local.get 0 + i32.const 16777215 + i32.gt_u + br_if 0 (;@2;) + local.get 0 + i32.const 38 + local.get 0 + i32.const 8 + i32.shr_u + i32.clz + local.tee 2 + i32.sub + i32.shr_u + i32.const 1 + i32.and + local.get 2 + i32.const 1 + i32.shl + i32.sub + i32.const 62 + i32.add + local.set 2 + end + local.get 1 + local.get 2 + i32.store offset=28 + local.get 1 + i64.const 0 + i64.store offset=16 align=4 + local.get 2 + i32.const 2 + i32.shl + i32.const 68104 + i32.add + local.set 5 + block ;; label = @2 + block ;; label = @3 + block ;; label = @4 + block ;; label = @5 + i32.const 0 + i32.load offset=67804 + local.tee 4 + i32.const 1 + local.get 2 + i32.shl + local.tee 3 + i32.and + br_if 0 (;@5;) + i32.const 0 + local.get 4 + local.get 3 + i32.or + i32.store offset=67804 + local.get 5 + local.get 1 + i32.store + i32.const 8 + local.set 0 + i32.const 24 + local.set 2 + br 1 (;@4;) + end + local.get 0 + i32.const 0 + i32.const 25 + local.get 2 + i32.const 1 + i32.shr_u + i32.sub + local.get 2 + i32.const 31 + i32.eq + select + i32.shl + local.set 2 + local.get 5 + i32.load + local.set 5 + loop ;; label = @5 + local.get 5 + local.tee 4 + i32.load offset=4 + i32.const -8 + i32.and + local.get 0 + i32.eq + br_if 2 (;@3;) + local.get 2 + i32.const 29 + i32.shr_u + local.set 5 + local.get 2 + i32.const 1 + i32.shl + local.set 2 + local.get 4 + local.get 5 + i32.const 4 + i32.and + i32.add + local.tee 3 + i32.load offset=16 + local.tee 5 + br_if 0 (;@5;) + end + local.get 3 + i32.const 16 + i32.add + local.get 1 + i32.store + i32.const 8 + local.set 0 + i32.const 24 + local.set 2 + local.get 4 + local.set 5 + end + local.get 1 + local.set 4 + local.get 1 + local.set 3 + br 1 (;@2;) + end + local.get 4 + i32.load offset=8 + local.tee 5 + local.get 1 + i32.store offset=12 + local.get 4 + local.get 1 + i32.store offset=8 + i32.const 0 + local.set 3 + i32.const 24 + local.set 0 + i32.const 8 + local.set 2 + end + local.get 1 + local.get 2 + i32.add + local.get 5 + i32.store + local.get 1 + local.get 4 + i32.store offset=12 + local.get 1 + local.get 0 + i32.add + local.get 3 + i32.store + i32.const 0 + i32.const 0 + i32.load offset=67832 + i32.const -1 + i32.add + local.tee 1 + i32.const -1 + local.get 1 + select + i32.store offset=67832 + end) + (func (;7;) (type 0) (result i32) + memory.size + i32.const 16 + i32.shl) + (func (;8;) (type 1) (param i32) (result i32) + i32.const 0) + (func (;9;) (type 1) (param i32) (result i32) + (local i32 i32) + i32.const 0 + i32.load offset=67764 + local.tee 1 + local.get 0 + i32.const 7 + i32.add + i32.const -8 + i32.and + local.tee 2 + i32.add + local.set 0 + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 2 + i32.eqz + br_if 0 (;@3;) + local.get 0 + local.get 1 + i32.le_u + br_if 1 (;@2;) + end + local.get 0 + call 7 + i32.le_u + br_if 1 (;@1;) + local.get 0 + call 8 + br_if 1 (;@1;) + end + call 3 + i32.const 48 + i32.store + i32.const -1 + return + end + i32.const 0 + local.get 0 + i32.store offset=67764 + local.get 1) + (func (;10;) (type 2) + i32.const 65536 + global.set 2 + i32.const 0 + i32.const 15 + i32.add + i32.const -16 + i32.and + global.set 1) + (func (;11;) (type 0) (result i32) + global.get 0 + global.get 1 + i32.sub) + (func (;12;) (type 0) (result i32) + global.get 2) + (func (;13;) (type 0) (result i32) + global.get 1) + (func (;14;) (type 3) (param i32) + local.get 0 + global.set 0) + (func (;15;) (type 1) (param i32) (result i32) + (local i32 i32) + global.get 0 + local.get 0 + i32.sub + i32.const -16 + i32.and + local.tee 1 + global.set 0 + local.get 1) + (func (;16;) (type 0) (result i32) + global.get 0) + (func (;17;) (type 6) (param i32 i32) (result i32) + i32.const 0 + local.get 0 + local.get 0 + i32.const 153 + i32.gt_u + select + i32.const 1 + i32.shl + i32.const 67456 + i32.add + i32.load16_u + i32.const 65536 + i32.add) + (func (;18;) (type 1) (param i32) (result i32) + local.get 0 + local.get 0 + call 17) + (table (;0;) 2 2 funcref) + (memory (;0;) 258 258) + (global (;0;) (mut i32) (i32.const 65536)) + (global (;1;) (mut i32) (i32.const 0)) + (global (;2;) (mut i32) (i32.const 0)) + (export "memory" (memory 0)) + (export "initSize" (func 1)) + (export "malloc" (func 4)) + (export "__indirect_function_table" (table 0)) + (export "_initialize" (func 2)) + (export "strerror" (func 18)) + (export "free" (func 6)) + (export "emscripten_stack_init" (func 10)) + (export "emscripten_stack_get_free" (func 11)) + (export "emscripten_stack_get_base" (func 12)) + (export "emscripten_stack_get_end" (func 13)) + (export "_emscripten_stack_restore" (func 14)) + (export "_emscripten_stack_alloc" (func 15)) + (export "emscripten_stack_get_current" (func 16)) + (elem (;0;) (i32.const 1) func 0) + (data (;0;) (i32.const 65536) "No error information\00Illegal byte sequence\00Domain error\00Result not representable\00Not a tty\00Permission denied\00Operation not permitted\00No such file or directory\00No such process\00File exists\00Value too large for data type\00No space left on device\00Out of memory\00Resource busy\00Interrupted system call\00Resource temporarily unavailable\00Invalid seek\00Cross-device link\00Read-only file system\00Directory not empty\00Connection reset by peer\00Operation timed out\00Connection refused\00Host is down\00Host is unreachable\00Address in use\00Broken pipe\00I/O error\00No such device or address\00Block device required\00No such device\00Not a directory\00Is a directory\00Text file busy\00Exec format error\00Invalid argument\00Argument list too long\00Symbolic link loop\00Filename too long\00Too many open files in system\00No file descriptors available\00Bad file descriptor\00No child process\00Bad address\00File too large\00Too many links\00No locks available\00Resource deadlock would occur\00State not recoverable\00Previous owner died\00Operation canceled\00Function not implemented\00No message of desired type\00Identifier removed\00Device not a stream\00No data available\00Device timeout\00Out of streams resources\00Link has been severed\00Protocol error\00Bad message\00File descriptor in bad state\00Not a socket\00Destination address required\00Message too large\00Protocol wrong type for socket\00Protocol not available\00Protocol not supported\00Socket type not supported\00Not supported\00Protocol family not supported\00Address family not supported by protocol\00Address not available\00Network is down\00Network unreachable\00Connection reset by network\00Connection aborted\00No buffer space available\00Socket is connected\00Socket not connected\00Cannot send after socket shutdown\00Operation already in progress\00Operation in progress\00Stale file handle\00Remote I/O error\00Quota exceeded\00No medium found\00Wrong medium type\00Multihop attempted\00Required key not available\00Key has expired\00Key has been revoked\00Key was rejected by service\00\00\00\00\00\00\00\00\00\a5\02[\00\f0\01\b5\05\8c\05%\01\83\06\1d\03\94\04\ff\00\c7\031\03\0b\06\bc\01\8f\01\7f\03\ca\04+\00\da\06\af\00B\03N\03\dc\01\0e\04\15\00\a1\06\0d\01\94\02\0b\028\06d\02\bc\02\ff\02]\03\e7\04\0b\07\cf\02\cb\05\ef\05\db\05\e1\02\1e\06E\02\85\00\82\02l\03o\04\f1\00\f3\03\18\05\d9\00\da\03L\06T\02{\01\9d\03\bd\04\00\00Q\00\15\02\bb\00\b3\03m\00\ff\01\85\04/\05\f9\048\00e\01F\01\9f\00\b7\06\a8\01s\02S\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00!\04\00\00\00\00\00\00\00\00/\02\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\005\04G\04V\04\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\a0\04\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00F\05`\05n\05a\06\00\00\cf\01\00\00\00\00\00\00\00\00\c9\06\e9\06\f9\06\1e\079\07I\07^\07") + (data (;1;) (i32.const 67764) "\d0\0a\01\00")) diff --git a/package-lock.json b/package-lock.json index b469309a..4de74a83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,31 +10,23 @@ "dependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@types/react-modal": "^3.16.3", - "framer-motion": "^11.11.17", - "prettier-plugin-emotion-order": "^1.0.1", + "framer-motion": "^11.18.2", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-horizontal-scrolling-menu": "^8.2.0", "react-query": "^3.39.3", - "react-router-dom": "^6.27.0", - "recoil": "^0.7.7", + "react-router": "^6.30.3", + "react-router-dom": "^6.30.3", "vite-plugin-radar": "^0.9.6" }, "devDependencies": { "@eslint/js": "^9.13.0", - "@svgr/rollup": "^8.1.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@types/emscripten": "^1.39.13", "@types/node": "^22.10.1", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^8.12.2", "@typescript-eslint/parser": "^8.12.2", - "@vite-pwa/assets-generator": "^0.2.6", "@vitejs/plugin-react-swc": "^3.7.1", - "assemblyscript": "^0.27.31", - "concurrently": "^9.1.0", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.31.0", @@ -48,78 +40,58 @@ "prettier": "^3.3.3", "prettier-plugin-css-order": "^2.1.2", "prettier-plugin-emotion-order": "^1.1.0", - "prettier-plugin-style-order": "^0.2.2", - "react-error-boundary": "^4.1.2", + "rollup-plugin-visualizer": "^6.0.5", + "svgo": "^4.0.0", "typescript": "^5.7.2", "typescript-eslint": "^8.11.0", "vite": "^5.4.11", - "vite-plugin": "^0.0.0", "vite-plugin-pwa": "^0.21.0", "vite-plugin-svgr": "^4.3.0", "vite-plugin-wasm": "^3.4.1" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@antfu/utils": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", - "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -138,56 +110,70 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, - "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", - "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" + "node_modules/@babel/core/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", + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">=6.9.0" + "node": ">=6" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "node_modules/@babel/core/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/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", - "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -196,18 +182,29 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/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/@babel/helper-create-class-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", - "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.25.9", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -217,14 +214,25 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-class-features-plugin/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/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", - "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.1.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -234,17 +242,28 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-create-regexp-features-plugin/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/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -277,6 +296,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-hoist-variables": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", @@ -291,39 +319,42 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -333,35 +364,38 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -371,14 +405,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", - "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -387,27 +422,15 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", - "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -427,63 +450,69 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -493,13 +522,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -509,12 +539,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -524,12 +555,13 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -539,14 +571,15 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -556,13 +589,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -576,6 +610,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" }, @@ -584,12 +619,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -599,42 +635,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -648,6 +655,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -660,12 +668,13 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -675,14 +684,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", - "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz", + "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -692,14 +702,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -709,12 +720,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", - "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -724,12 +736,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -739,13 +752,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -755,13 +769,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -771,17 +786,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -790,23 +806,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-classes/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -816,12 +824,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -831,13 +841,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -847,12 +858,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -862,13 +874,14 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", + "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -878,12 +891,30 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -893,13 +924,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", - "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -909,12 +940,13 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -924,13 +956,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", - "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -940,14 +973,15 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -957,12 +991,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -972,12 +1007,13 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -987,12 +1023,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1002,12 +1039,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1017,13 +1055,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1033,14 +1072,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", - "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1050,15 +1089,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1068,13 +1108,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1084,13 +1125,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1100,12 +1142,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1115,12 +1158,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", - "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1130,12 +1174,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1145,14 +1190,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1162,13 +1210,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1178,12 +1227,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1193,13 +1243,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1209,12 +1260,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1224,13 +1276,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1240,14 +1293,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1257,12 +1311,13 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1271,13 +1326,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz", - "integrity": "sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz", + "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1286,32 +1342,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", - "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", - "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1320,13 +1375,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", - "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1335,14 +1391,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", - "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1351,14 +1408,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "regenerator-transform": "^0.15.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1367,29 +1424,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1398,13 +1440,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1413,14 +1456,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1429,13 +1472,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1444,13 +1489,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", - "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1459,32 +1506,100 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", - "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz", - "integrity": "sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==", + "node_modules/@babel/preset-env": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", + "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" + "@babel/compat-data": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1493,150 +1608,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "node_modules/@babel/preset-env/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, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", - "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.25.9", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.25.9", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.25.9", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.25.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.25.9", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.25.9", - "@babel/plugin-transform-typeof-symbol": "^7.25.9", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.6", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.38.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, "node_modules/@babel/preset-modules": { @@ -1644,6 +1623,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -1653,122 +1633,71 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/preset-react": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.9.tgz", - "integrity": "sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-transform-react-display-name": "^7.25.9", - "@babel/plugin-transform-react-jsx": "^7.25.9", - "@babel/plugin-transform-react-jsx-development": "^7.25.9", - "@babel/plugin-transform-react-pure-annotations": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz", - "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.25.9", - "@babel/plugin-transform-typescript": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", - "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", - "debug": "^4.3.1", - "globals": "^11.1.0" + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@canvas/image-data": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.0.0.tgz", - "integrity": "sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==", - "dev": true - }, "node_modules/@emotion/babel-plugin": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", - "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", - "@emotion/serialize": "^1.2.0", + "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", @@ -1778,13 +1707,14 @@ } }, "node_modules/@emotion/cache": { - "version": "11.13.1", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", - "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", - "@emotion/utils": "^1.4.0", + "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } @@ -1792,12 +1722,14 @@ "node_modules/@emotion/hash": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", - "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -1805,20 +1737,22 @@ "node_modules/@emotion/memoize": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" }, "node_modules/@emotion/react": { - "version": "11.13.3", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", - "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.12.0", - "@emotion/cache": "^11.13.0", - "@emotion/serialize": "^1.3.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", - "@emotion/utils": "^1.4.0", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, @@ -1832,33 +1766,36 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", - "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.1", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "node_modules/@emotion/sheet": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", - "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" }, "node_modules/@emotion/styled": { - "version": "11.13.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", - "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.12.0", + "@emotion/babel-plugin": "^11.13.5", "@emotion/is-prop-valid": "^1.3.0", - "@emotion/serialize": "^1.3.0", - "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", - "@emotion/utils": "^1.4.0" + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", @@ -1873,25 +1810,29 @@ "node_modules/@emotion/unitless": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", - "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@emotion/utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", - "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" }, "node_modules/@emotion/weak-memoize": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", @@ -1900,6 +1841,7 @@ "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "aix" @@ -1915,6 +1857,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -1930,6 +1873,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -1945,6 +1889,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -1960,6 +1905,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1975,6 +1921,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1990,6 +1937,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2005,6 +1953,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -2020,6 +1969,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2035,6 +1985,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2050,6 +2001,7 @@ "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2065,6 +2017,7 @@ "cpu": [ "loong64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2080,6 +2033,7 @@ "cpu": [ "mips64el" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2095,6 +2049,7 @@ "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2110,6 +2065,7 @@ "cpu": [ "riscv64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2125,6 +2081,7 @@ "cpu": [ "s390x" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2140,6 +2097,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -2155,6 +2113,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -2170,6 +2129,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -2185,6 +2145,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "sunos" @@ -2200,6 +2161,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -2215,6 +2177,7 @@ "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -2230,6 +2193,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -2239,25 +2203,30 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2267,6 +2236,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2286,10 +2256,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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" @@ -2300,6 +2271,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -2310,11 +2282,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/@eslint/eslintrc/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" }, @@ -2322,13 +2305,30 @@ "node": "*" } }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/js": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", - "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@humanwhocodes/config-array": { @@ -2337,6 +2337,7 @@ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", @@ -2347,10 +2348,11 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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" @@ -2361,6 +2363,7 @@ "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" }, @@ -2373,6 +2376,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -2386,121 +2390,265 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "engines": { - "node": ">=6.0.0" - } + "dev": true, + "license": "BSD-3-Clause" }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "devOptional": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "node": "20 || >=22" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "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==", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", "dev": true, + "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@isaacs/balanced-match": "^4.0.1" }, "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, - "engines": { - "node": ">= 8" + "node": "20 || >=22" } }, - "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==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "node_modules/@isaacs/cliui/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.20.0 || ^14.18.0 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@remix-run/router": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", - "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "node_modules/@isaacs/cliui/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": ">=14.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/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/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", - "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", @@ -2525,6 +2673,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", "dev": true, + "license": "MIT", "dependencies": { "serialize-javascript": "^6.0.1", "smob": "^1.0.0", @@ -2543,10 +2692,11 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", - "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", @@ -2565,192 +2715,325 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", "cpu": [ "arm64" ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "cpu": [ + "loong64" + ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", "cpu": [ "ppc64" ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", "cpu": [ "riscv64" ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", "cpu": [ "s390x" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", "cpu": [ "ia32" ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "cpu": [ + "x64" + ], + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -2760,13 +3043,15 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", @@ -2774,11 +3059,25 @@ "string.prototype.matchall": "^4.0.6" } }, + "node_modules/@surma/rollup-plugin-off-main-thread/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", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -2795,6 +3094,7 @@ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -2811,6 +3111,7 @@ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -2827,6 +3128,7 @@ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -2843,6 +3145,7 @@ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -2859,6 +3162,7 @@ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -2875,6 +3179,7 @@ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -2891,6 +3196,7 @@ "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -2907,6 +3213,7 @@ "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", "dev": true, + "license": "MIT", "dependencies": { "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", @@ -2933,7 +3240,7 @@ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -2949,11 +3256,39 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@svgr/hast-util-to-babel-ast": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.21.3", "entities": "^4.4.0" @@ -2971,6 +3306,7 @@ "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -2988,60 +3324,16 @@ "@svgr/core": "*" } }, - "node_modules/@svgr/plugin-svgo": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", - "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", - "dev": true, - "dependencies": { - "cosmiconfig": "^8.1.3", - "deepmerge": "^4.3.1", - "svgo": "^3.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/rollup": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/rollup/-/rollup-8.1.0.tgz", - "integrity": "sha512-0XR1poYvPQoPpmfDYLEqUGu5ePAQ4pdgN3VFsZBNAeze7qubVpsIY1o1R6PZpKep/DKu33GSm2NhwpCLkMs2Cw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.21.3", - "@babel/plugin-transform-react-constant-elements": "^7.21.3", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.0", - "@rollup/pluginutils": "^5.0.2", - "@svgr/core": "8.1.0", - "@svgr/plugin-jsx": "8.1.0", - "@svgr/plugin-svgo": "8.1.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, "node_modules/@swc/core": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.35.tgz", - "integrity": "sha512-3cUteCTbr2r5jqfgx0r091sfq5Mgh6F1SQh8XAOnSvtKzwv2bC31mvBHVAieD1uPa2kHJhLav20DQgXOhpEitw==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.10.tgz", + "integrity": "sha512-udNofxftduMUEv7nqahl2nvodCiCDQ4Ge0ebzsEm6P8s0RC2tBM0Hqx0nNF5J/6t9uagFJyWIDjXy3IIWMHDJw==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.13" + "@swc/types": "^0.1.25" }, "engines": { "node": ">=10" @@ -3051,19 +3343,19 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.7.35", - "@swc/core-darwin-x64": "1.7.35", - "@swc/core-linux-arm-gnueabihf": "1.7.35", - "@swc/core-linux-arm64-gnu": "1.7.35", - "@swc/core-linux-arm64-musl": "1.7.35", - "@swc/core-linux-x64-gnu": "1.7.35", - "@swc/core-linux-x64-musl": "1.7.35", - "@swc/core-win32-arm64-msvc": "1.7.35", - "@swc/core-win32-ia32-msvc": "1.7.35", - "@swc/core-win32-x64-msvc": "1.7.35" + "@swc/core-darwin-arm64": "1.15.10", + "@swc/core-darwin-x64": "1.15.10", + "@swc/core-linux-arm-gnueabihf": "1.15.10", + "@swc/core-linux-arm64-gnu": "1.15.10", + "@swc/core-linux-arm64-musl": "1.15.10", + "@swc/core-linux-x64-gnu": "1.15.10", + "@swc/core-linux-x64-musl": "1.15.10", + "@swc/core-win32-arm64-msvc": "1.15.10", + "@swc/core-win32-ia32-msvc": "1.15.10", + "@swc/core-win32-x64-msvc": "1.15.10" }, "peerDependencies": { - "@swc/helpers": "*" + "@swc/helpers": ">=0.5.17" }, "peerDependenciesMeta": { "@swc/helpers": { @@ -3072,13 +3364,14 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.35.tgz", - "integrity": "sha512-BQSSozVxjxS+SVQz6e3GC/+OBWGIK3jfe52pWdANmycdjF3ch7lrCKTHTU7eHwyoJ96mofszPf5AsiVJF34Fwg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.10.tgz", + "integrity": "sha512-U72pGqmJYbjrLhMndIemZ7u9Q9owcJczGxwtfJlz/WwMaGYAV/g4nkGiUVk/+QSX8sFCAjanovcU1IUsP2YulA==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -3088,13 +3381,14 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.35.tgz", - "integrity": "sha512-44TYdKN/EWtkU88foXR7IGki9JzhEJzaFOoPevfi9Xe7hjAD/x2+AJOWWqQNzDPMz9+QewLdUVLyR6s5okRgtg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.10.tgz", + "integrity": "sha512-NZpDXtwHH083L40xdyj1sY31MIwLgOxKfZEAGCI8xHXdHa+GWvEiVdGiu4qhkJctoHFzAEc7ZX3GN5phuJcPuQ==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -3104,13 +3398,14 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.35.tgz", - "integrity": "sha512-ccfA5h3zxwioD+/z/AmYtkwtKz9m4rWTV7RoHq6Jfsb0cXHrd6tbcvgqRWXra1kASlE+cDWsMtEZygs9dJRtUQ==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.10.tgz", + "integrity": "sha512-ioieF5iuRziUF1HkH1gg1r93e055dAdeBAPGAk40VjqpL5/igPJ/WxFHGvc6WMLhUubSJI4S0AiZAAhEAp1jDg==", "cpu": [ "arm" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -3120,13 +3415,14 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.35.tgz", - "integrity": "sha512-hx65Qz+G4iG/IVtxJKewC5SJdki8PAPFGl6gC/57Jb0+jA4BIoGLD/J3Q3rCPeoHfdqpkCYpahtyUq8CKx41Jg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.10.tgz", + "integrity": "sha512-tD6BClOrxSsNus9cJL7Gxdv7z7Y2hlyvZd9l0NQz+YXzmTWqnfzLpg16ovEI7gknH2AgDBB5ywOsqu8hUgSeEQ==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -3136,13 +3432,14 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.35.tgz", - "integrity": "sha512-kL6tQL9No7UEoEvDRuPxzPTpxrvbwYteNRbdChSSP74j13/55G2/2hLmult5yFFaWuyoyU/2lvzjRL/i8OLZxg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.10.tgz", + "integrity": "sha512-4uAHO3nbfbrTcmO/9YcVweTQdx5fN3l7ewwl5AEK4yoC4wXmoBTEPHAVdKNe4r9+xrTgd4BgyPsy0409OjjlMw==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -3152,13 +3449,14 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.35.tgz", - "integrity": "sha512-Ke4rcLQSwCQ2LHdJX1FtnqmYNQ3IX6BddKlUtS7mcK13IHkQzZWp0Dcu6MgNA3twzb/dBpKX5GLy07XdGgfmyw==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.10.tgz", + "integrity": "sha512-W0h9ONNw1pVIA0cN7wtboOSTl4Jk3tHq+w2cMPQudu9/+3xoCxpFb9ZdehwCAk29IsvdWzGzY6P7dDVTyFwoqg==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -3168,13 +3466,14 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.35.tgz", - "integrity": "sha512-T30tlLnz0kYyDFyO5RQF5EQ4ENjW9+b56hEGgFUYmfhFhGA4E4V67iEx7KIG4u0whdPG7oy3qjyyIeTb7nElEw==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.10.tgz", + "integrity": "sha512-XQNZlLZB62S8nAbw7pqoqwy91Ldy2RpaMRqdRN3T+tAg6Xg6FywXRKCsLh6IQOadr4p1+lGnqM/Wn35z5a/0Vw==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -3184,13 +3483,14 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.35.tgz", - "integrity": "sha512-CfM/k8mvtuMyX+okRhemfLt784PLS0KF7Q9djA8/Dtavk0L5Ghnq+XsGltO3d8B8+XZ7YOITsB14CrjehzeHsg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.10.tgz", + "integrity": "sha512-qnAGrRv5Nj/DATxAmCnJQRXXQqnJwR0trxLndhoHoxGci9MuguNIjWahS0gw8YZFjgTinbTxOwzatkoySihnmw==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -3200,13 +3500,14 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.35.tgz", - "integrity": "sha512-ATB3uuH8j/RmS64EXQZJSbo2WXfRNpTnQszHME/sGaexsuxeijrp3DTYSFAA3R2Bu6HbIIX6jempe1Au8I3j+A==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.10.tgz", + "integrity": "sha512-i4X/q8QSvzVlaRtv1xfnfl+hVKpCfiJ+9th484rh937fiEZKxZGf51C+uO0lfKDP1FfnT6C1yBYwHy7FLBVXFw==", "cpu": [ "ia32" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -3216,13 +3517,14 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.7.35", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.35.tgz", - "integrity": "sha512-iDGfQO1571NqWUXtLYDhwIELA/wadH42ioGn+J9R336nWx40YICzy9UQyslWRhqzhQ5kT+QXAW/MoCWc058N6Q==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.10.tgz", + "integrity": "sha512-HvY8XUFuoTXn6lSccDLYFlXv1SU/PzYi4PyUqGT++WfTnbw/68N/7BdUZqglGRwiSqr0qhYt/EhmBpULj0J9rA==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -3235,13 +3537,15 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/@swc/types": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", - "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" } @@ -3308,16 +3612,16 @@ } }, "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -3325,23 +3629,23 @@ } }, "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/@babel/traverse/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -3388,108 +3692,94 @@ "node": ">=4" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@types/emscripten": { - "version": "1.39.13", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.13.tgz", - "integrity": "sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "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 + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", "devOptional": true, "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", - "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-modal": { - "version": "3.16.3", - "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz", - "integrity": "sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==", "license": "MIT", - "dependencies": { - "@types/react": "*" + "peerDependencies": { + "@types/react": "^18.0.0" } }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", - "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", + "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.12.2", - "@typescript-eslint/type-utils": "8.12.2", - "@typescript-eslint/utils": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/type-utils": "8.53.1", + "@typescript-eslint/utils": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3499,27 +3789,24 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.53.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz", - "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", + "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.12.2", - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/typescript-estree": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3529,22 +3816,41 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", + "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.53.1", + "@typescript-eslint/types": "^8.53.1", + "debug": "^4.4.3" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", - "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", + "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2" + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3554,16 +3860,35 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", + "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz", - "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", + "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.12.2", - "@typescript-eslint/utils": "8.12.2", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/utils": "8.53.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3572,17 +3897,17 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", - "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", + "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3592,19 +3917,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", - "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", + "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/project-service": "8.53.1", + "@typescript-eslint/tsconfig-utils": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/visitor-keys": "8.53.1", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3613,34 +3940,21 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", - "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", + "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.12.2", - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/typescript-estree": "8.12.2" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.53.1", + "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3650,17 +3964,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", - "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", + "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.12.2", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.53.1", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3670,53 +3986,46 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/@vite-pwa/assets-generator": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-0.2.6.tgz", - "integrity": "sha512-kK44dXltvoubEo5B+6tCGjUrOWOE1+dA4DForbFpO1rKy2wSkAVGrs8tyfN6DzTig89/QKyV8XYodgmaKyrYng==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "peer": true, - "dependencies": { - "cac": "^6.7.14", - "colorette": "^2.0.20", - "consola": "^3.2.3", - "sharp": "^0.32.6", - "sharp-ico": "^0.1.5", - "unconfig": "^0.3.11" - }, - "bin": { - "pwa-assets-generator": "bin/pwa-assets-generator.mjs" - }, + "license": "Apache-2.0", "engines": { - "node": ">=16.14.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, "node_modules/@vitejs/plugin-react-swc": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.1.tgz", - "integrity": "sha512-vgWOY0i1EROUK0Ctg1hwhtC3SdcDjZcdit4Ups4aPkDcB1jYhmo+RMYWY87cmXMhvtD5uf8lV89j2w16vkdSVg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz", + "integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==", "dev": true, + "license": "MIT", "dependencies": { - "@swc/core": "^1.7.26" + "@rolldown/pluginutils": "1.0.0-beta.27", + "@swc/core": "^1.12.11" }, "peerDependencies": { - "vite": "^4 || ^5" + "vite": "^4 || ^5 || ^6 || ^7" } }, "node_modules/acorn": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", - "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, + "license": "MIT", "peer": true, "bin": { "acorn": "bin/acorn" @@ -3730,6 +4039,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -3739,6 +4049,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3755,6 +4066,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3764,6 +4076,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -3778,16 +4091,18 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "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": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { "node": ">= 0.4" @@ -3797,17 +4112,20 @@ } }, "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "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": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" + "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" @@ -3821,6 +4139,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -3837,17 +4156,19 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "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, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3857,15 +4178,16 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "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.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "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" @@ -3875,15 +4197,16 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "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.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "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" @@ -3897,6 +4220,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -3909,19 +4233,19 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "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.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "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" @@ -3930,40 +4254,29 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/assemblyscript": { - "version": "0.27.31", - "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.27.31.tgz", - "integrity": "sha512-Ra8kiGhgJQGZcBxjtMcyVRxOEJZX64kd+XGpjWzjcjgxWJVv+CAQO0aDBk4GQVhjYbOkATarC83mHjAVGtwPBQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "binaryen": "116.0.0-nightly.20240114", - "long": "^5.2.1" - }, - "bin": { - "asc": "bin/asc.js", - "asinit": "bin/asinit.js" - }, - "engines": { - "node": ">=16", - "npm": ">=7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/assemblyscript" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true + "dev": true, + "license": "MIT" + }, + "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/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, + "license": "ISC", "engines": { "node": ">= 4.0.0" } @@ -3973,6 +4286,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" }, @@ -3983,16 +4297,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", - "dev": true - }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -4003,55 +4312,53 @@ "npm": ">=6" } }, - "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-plugin-polyfill-corejs2/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/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" + "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4060,135 +4367,50 @@ "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==" - }, - "node_modules/bare-events": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", - "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", - "dev": true, - "optional": true - }, - "node_modules/bare-fs": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", - "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", - "dev": true, - "optional": true, - "dependencies": { - "bare-events": "^2.0.0", - "bare-path": "^2.0.0", - "bare-stream": "^2.0.0" - } - }, - "node_modules/bare-os": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", - "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", - "dev": true, - "optional": true - }, - "node_modules/bare-path": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", - "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", - "dev": true, - "optional": true, - "dependencies": { - "bare-os": "^2.1.0" - } + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, - "node_modules/bare-stream": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.4.0.tgz", - "integrity": "sha512-sd96/aZ8LjF1uJbEHzIo1LrERPKRFPEy1nZ1eOILftBxrVsFDAQkimHIIq87xrHcubzjNeETsD9PwN0wp+vLiQ==", + "node_modules/baseline-browser-mapping": { + "version": "2.9.17", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.17.tgz", + "integrity": "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==", "dev": true, - "optional": true, - "dependencies": { - "streamx": "^2.20.0" + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "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" - } - ] - }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", "engines": { "node": ">=0.6" } }, - "node_modules/binaryen": { - "version": "116.0.0-nightly.20240114", - "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0-nightly.20240114.tgz", - "integrity": "sha512-0GZrojJnuhoe+hiwji7QFaL3tBlJoA+KFUN7ouYSDGZLSo9CKM8swQX8n/UcbR0d1VuZKU+nhogNzv423JEu5A==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "wasm-opt": "bin/wasm-opt", - "wasm2js": "bin/wasm2js" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, - "node_modules/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, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/broadcast-channel": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.7.2", "detect-node": "^2.1.0", @@ -4201,9 +4423,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -4215,74 +4437,75 @@ "url": "https://tidelift.com/funding/github/npm/browserslist" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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" + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", + "peer": true, "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true + "devOptional": true, + "license": "MIT" }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "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": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "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" @@ -4295,6 +4518,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4304,6 +4528,7 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -4312,9 +4537,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001677", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", - "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "dev": true, "funding": [ { @@ -4329,13 +4554,15 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4347,12 +4574,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4368,24 +4589,12 @@ "node": ">=12" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -4397,31 +4606,17 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "dev": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "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/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=16" } }, "node_modules/common-tags": { @@ -4429,83 +4624,31 @@ "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4.0.0" } }, - "node_modules/compute-scroll-into-view": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", - "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/concurrently": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.0.tgz", - "integrity": "sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" - } - }, - "node_modules/concurrently/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/consola": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", - "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", - "dev": true, - "engines": { - "node": "^14.18.0 || >=16.10.0" - } + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", - "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.24.2" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", @@ -4513,36 +4656,27 @@ } }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" }, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=10" } }, "node_modules/cross-spawn": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", - "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -4557,14 +4691,15 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz", + "integrity": "sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==", "dev": true, "license": "ISC", "engines": { @@ -4575,10 +4710,11 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -4591,12 +4727,13 @@ } }, "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, + "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -4604,10 +4741,11 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -4620,6 +4758,7 @@ "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "dev": true, + "license": "MIT", "dependencies": { "css-tree": "~2.2.0" }, @@ -4633,6 +4772,7 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "dev": true, + "license": "MIT", "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" @@ -4646,22 +4786,25 @@ "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true + "dev": true, + "license": "CC0-1.0" }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" }, "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "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-bind": "^1.0.6", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4671,29 +4814,31 @@ } }, "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "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-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/inspect-js" } }, "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "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-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" }, @@ -4705,9 +4850,10 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -4720,68 +4866,19 @@ } } }, - "node_modules/decode-bmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/decode-bmp/-/decode-bmp-0.2.1.tgz", - "integrity": "sha512-NiOaGe+GN0KJqi2STf24hfMkFitDUaIoUU3eKvP/wAbLe8o6FuW5n/x7MHPR0HKvBokp6MQY/j7w8lewEeVCIA==", - "dev": true, - "dependencies": { - "@canvas/image-data": "^1.0.0", - "to-data-view": "^1.1.0" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/decode-ico": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/decode-ico/-/decode-ico-0.4.1.tgz", - "integrity": "sha512-69NZfbKIzux1vBOd31al3XnMnH+2mqDhEgLdpygErm4d60N+UwA5Sq5WFjmEDQzumgB9fElojGwWG0vybVfFmA==", - "dev": true, - "dependencies": { - "@canvas/image-data": "^1.0.0", - "decode-bmp": "^0.2.0", - "to-data-view": "^1.1.0" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4791,6 +4888,7 @@ "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", @@ -4803,11 +4901,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "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", @@ -4820,31 +4929,18 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "license": "MIT" }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -4857,6 +4953,7 @@ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -4876,13 +4973,15 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -4894,10 +4993,11 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -4912,16 +5012,40 @@ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" }, @@ -4933,10 +5057,11 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.50", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.50.tgz", - "integrity": "sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==", - "dev": true + "version": "1.5.278", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz", + "integrity": "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -4952,20 +5077,12 @@ "dev": true, "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -4974,65 +5091,75 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, + "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "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.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", + "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.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", + "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.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "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.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "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" @@ -5042,13 +5169,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5058,40 +5183,45 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/es-iterator-helpers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.1.0.tgz", - "integrity": "sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", + "es-abstract": "^1.24.1", "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", + "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", + "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.3", - "safe-array-concat": "^1.1.2" + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -5100,37 +5230,44 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "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.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "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.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -5144,6 +5281,7 @@ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -5181,6 +5319,7 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5189,6 +5328,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -5202,6 +5342,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -5254,10 +5395,11 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, + "license": "MIT", "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -5271,6 +5413,7 @@ "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", @@ -5282,15 +5425,17 @@ "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.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "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" }, @@ -5308,34 +5453,36 @@ "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.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "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", "dependencies": { "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", + "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.0", + "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", - "is-core-module": "^2.15.1", + "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.0", + "object.values": "^1.2.1", "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", + "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "engines": { @@ -5346,10 +5493,11 @@ } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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" @@ -5360,6 +5508,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" } @@ -5369,6 +5518,7 @@ "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" }, @@ -5381,6 +5531,7 @@ "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" }, @@ -5388,14 +5539,25 @@ "node": "*" } }, + "node_modules/eslint-plugin-import/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/eslint-plugin-prettier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", - "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "dev": true, + "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.9.1" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -5406,7 +5568,7 @@ "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", - "eslint-config-prettier": "*", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "peerDependenciesMeta": { @@ -5419,28 +5581,29 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.37.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", - "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, + "license": "MIT", "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.2", + "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.1.0", + "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.8", + "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11", + "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "engines": { @@ -5451,10 +5614,11 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz", - "integrity": "sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -5463,19 +5627,21 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.12.tgz", - "integrity": "sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==", + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", "dev": true, + "license": "MIT", "peerDependencies": { - "eslint": ">=7" + "eslint": ">=8.40" } }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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" @@ -5486,6 +5652,7 @@ "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" }, @@ -5498,6 +5665,7 @@ "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" }, @@ -5510,6 +5678,7 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, + "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -5522,11 +5691,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-react/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/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -5543,6 +5723,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5555,15 +5736,17 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "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" @@ -5574,6 +5757,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -5584,11 +5768,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/eslint/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" }, @@ -5596,11 +5791,25 @@ "node": "*" } }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -5614,10 +5823,11 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -5630,6 +5840,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -5642,6 +5853,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -5650,104 +5862,83 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "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.4" - }, - "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, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } + "license": "Apache-2.0" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", - "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", - "dev": true + "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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, "node_modules/fdir": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", - "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", + "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" }, @@ -5762,6 +5953,7 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" }, @@ -5774,6 +5966,7 @@ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" } @@ -5783,6 +5976,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -5790,28 +5984,18 @@ "node": ">=10" } }, - "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, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -5828,6 +6012,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -5838,32 +6023,59 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "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/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "is-callable": "^1.1.3" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/framer-motion": { - "version": "11.11.17", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.11.17.tgz", - "integrity": "sha512-O8QzvoKiuzI5HSAHbcYuL6xU+ZLXbrH7C8Akaato4JzQbX2ULNeniqC2Vo5eiCtFktX9XsJ+7nUhxcl2E2IjpA==", + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", "license": "MIT", "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/is-prop-valid": { @@ -5877,17 +6089,12 @@ } } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, + "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -5901,13 +6108,15 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -5920,20 +6129,24 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "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.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "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" @@ -5947,15 +6160,27 @@ "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", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -5971,16 +6196,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5993,17 +6224,33 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true + "dev": true, + "license": "ISC" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "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-bind": "^1.0.5", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -6012,17 +6259,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "dev": true - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6043,6 +6285,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -6051,9 +6294,10 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6063,6 +6307,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6071,10 +6316,11 @@ } }, "node_modules/globals": { - "version": "15.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", - "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -6087,6 +6333,7 @@ "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" @@ -6099,12 +6346,13 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6114,24 +6362,25 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/hamt_plus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + "dev": true, + "license": "MIT" }, "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "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" } @@ -6141,6 +6390,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6150,6 +6400,7 @@ "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" }, @@ -6158,10 +6409,14 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "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" }, @@ -6170,10 +6425,11 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6186,6 +6442,7 @@ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -6200,6 +6457,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -6211,55 +6469,33 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", "dependencies": { "react-is": "^16.7.0" } }, - "node_modules/ico-endec": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ico-endec/-/ico-endec-0.1.6.tgz", - "integrity": "sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ==", - "dev": true - }, "node_modules/idb": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "dev": true - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "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": "ISC" }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6276,6 +6512,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -6285,6 +6522,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -6293,36 +6531,34 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "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 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "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.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "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.2", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -6334,15 +6570,21 @@ "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==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "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": { - "has-tostringtag": "^1.0.0" + "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.4" @@ -6352,25 +6594,30 @@ } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "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": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "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", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6384,6 +6631,7 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6392,9 +6640,10 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -6406,11 +6655,14 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "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": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -6421,12 +6673,14 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "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": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6435,22 +6689,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/is-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "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-bind": "^1.0.2" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6467,12 +6742,17 @@ } }, "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "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": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "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": ">= 0.4" @@ -6486,6 +6766,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -6498,6 +6779,7 @@ "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", "engines": { "node": ">= 0.4" }, @@ -6509,13 +6791,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true + "dev": true, + "license": "MIT" }, "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", "engines": { "node": ">= 0.4" }, @@ -6523,22 +6807,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "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": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6552,6 +6829,7 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6561,18 +6839,22 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "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": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -6586,6 +6868,7 @@ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6595,6 +6878,7 @@ "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": ">= 0.4" }, @@ -6603,12 +6887,13 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "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-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -6622,6 +6907,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -6630,12 +6916,14 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6645,12 +6933,15 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "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": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6660,12 +6951,13 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "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, + "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -6679,6 +6971,7 @@ "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" }, @@ -6687,25 +6980,30 @@ } }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "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-bind": "^1.0.2" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "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", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -6714,72 +7012,83 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "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 + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/iterator.prototype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.3.tgz", - "integrity": "sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" + "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jake/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/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^1.1.7" + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" }, "engines": { - "node": "*" + "node": ">=10" } }, "node_modules/javascript-natural-sort": { @@ -6789,30 +7098,24 @@ "dev": true, "license": "MIT" }, - "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" - } - }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" }, "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==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "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==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -6821,9 +7124,10 @@ } }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -6835,48 +7139,55 @@ "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 + "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==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" }, "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 + "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 + "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==", + "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" - }, - "engines": { - "node": ">=6" } }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -6889,6 +7200,7 @@ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6898,6 +7210,7 @@ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, + "license": "MIT", "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -6913,6 +7226,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -6922,6 +7236,7 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -6931,6 +7246,7 @@ "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" @@ -6942,13 +7258,15 @@ "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==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "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" }, @@ -6960,40 +7278,38 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true + "dev": true, + "license": "MIT" }, "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 + "dev": true, + "license": "MIT" }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true - }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", "dev": true, - "license": "Apache-2.0" + "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -7006,6 +7322,7 @@ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -7015,6 +7332,7 @@ "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" } @@ -7024,6 +7342,7 @@ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", "dev": true, + "license": "MIT", "dependencies": { "sourcemap-codec": "^1.4.8" } @@ -7032,73 +7351,41 @@ "version": "6.3.4", "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", - "dependencies": { - "@babel/runtime": "^7.23.8", - "remove-accents": "0.5.0" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true - }, - "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, - "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, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" } }, - "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==", + "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==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">= 0.4" } }, + "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/microseconds": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", - "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==", + "license": "MIT" }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -7114,39 +7401,62 @@ "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/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" }, "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==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/nano-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "license": "ISC", "dependencies": { "big-integer": "^1.6.16" } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "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" }, @@ -7154,69 +7464,37 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", - "dev": true - }, "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 + "dev": true, + "license": "MIT" }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, + "license": "MIT", "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, - "node_modules/node-abi": { - "version": "3.71.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", - "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==", - "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true - }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true + "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/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -7229,15 +7507,17 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "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" }, @@ -7250,19 +7530,23 @@ "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.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "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.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -7273,14 +7557,16 @@ } }, "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -7291,6 +7577,7 @@ "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", @@ -7309,6 +7596,7 @@ "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", @@ -7319,12 +7607,14 @@ } }, "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "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.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, @@ -7338,21 +7628,42 @@ "node_modules/oblivious-set": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", - "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==", + "license": "MIT" }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "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", @@ -7365,11 +7676,30 @@ "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" }, @@ -7385,6 +7715,7 @@ "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" }, @@ -7395,10 +7726,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.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==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -7410,6 +7749,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -7428,6 +7768,7 @@ "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" } @@ -7436,6 +7777,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7445,6 +7787,7 @@ "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" } @@ -7452,12 +7795,41 @@ "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==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "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==", + "license": "MIT", "engines": { "node": ">=8" } @@ -7469,11 +7841,11 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -7482,18 +7854,19 @@ } }, "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "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.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -7511,7 +7884,7 @@ "license": "MIT", "peer": true, "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -7560,73 +7933,19 @@ } }, "node_modules/postcss-styled-syntax": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/postcss-styled-syntax/-/postcss-styled-syntax-0.7.0.tgz", - "integrity": "sha512-OeStzPkHJ1/WDGRKm/JuVK8UdJbjt3U7AFC+zUc9omJ79SaXSxWoy+PXxJz7t8vOO8HcUgCLndNEQfLvZ74TuQ==", + "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.6.3" + "typescript": "^5.7.3" }, "engines": { "node": ">=14.17" }, "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", - "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", - "dev": true, - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" + "postcss": "^8.5.1" } }, "node_modules/prelude-ls": { @@ -7634,15 +7953,17 @@ "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.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, + "license": "MIT", "peer": true, "bin": { "prettier": "bin/prettier.cjs" @@ -7655,10 +7976,11 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -7667,13 +7989,13 @@ } }, "node_modules/prettier-plugin-css-order": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-css-order/-/prettier-plugin-css-order-2.1.2.tgz", - "integrity": "sha512-vomxPjHI6pOMYcBuouSJHxxQClJXaUpU9rsV9IAO2wrSTZILRRlrxAAR8t9UF6wtczLkLfNRFUwM+ZbGXOONUA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-css-order/-/prettier-plugin-css-order-2.2.0.tgz", + "integrity": "sha512-GCkwEgQ2roT7le+zpUFQThPDO4x5EXcZmY9Rj6rvO++I/nATTGBWdZdsooha/BlvIBbZclJzXsgJdlKWrys9+w==", "dev": true, "license": "ISC", "dependencies": { - "css-declaration-sorter": "^7.1.1", + "css-declaration-sorter": "^7.3.0", "postcss-less": "^6.0.0", "postcss-scss": "^4.0.9" }, @@ -7720,74 +8042,12 @@ } } }, - "node_modules/prettier-plugin-style-order": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-style-order/-/prettier-plugin-style-order-0.2.2.tgz", - "integrity": "sha512-4rASdHONhHLNX0arKqEvjGOEAbkSZE3+GQPSaS8mf3en/spjeN0le5fV1yKltWwQeM4UPn7CkKzeD97a4Az/6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-sorting": "5.0.1", - "resolve-cwd": "^3.0.0" - }, - "peerDependencies": { - "prettier": ">= 1.13" - } - }, - "node_modules/prettier-plugin-style-order/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true, - "license": "ISC" - }, - "node_modules/prettier-plugin-style-order/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/prettier-plugin-style-order/node_modules/postcss-sorting": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-5.0.1.tgz", - "integrity": "sha512-Y9fUFkIhfrm6i0Ta3n+89j56EFqaNRdUKqXyRp6kvTcSXnmgEjaVowCXH+JBe9+YKWqd4nc28r2sgwnzJalccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14", - "postcss": "^7.0.17" - }, - "engines": { - "node": ">=8.7.0" - } - }, - "node_modules/prettier-plugin-style-order/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pretty-bytes": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", "dev": true, + "license": "MIT", "engines": { "node": "^14.13.1 || >=16.0.0" }, @@ -7800,27 +8060,19 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, + "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -7843,51 +8095,24 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "dev": true + ], + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "peer": true, "dependencies": { "loose-envify": "^1.1.0" @@ -7900,57 +8125,27 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-error-boundary": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.1.2.tgz", - "integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "peerDependencies": { - "react": ">=16.13.1" - } - }, - "node_modules/react-horizontal-scrolling-menu": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/react-horizontal-scrolling-menu/-/react-horizontal-scrolling-menu-8.2.0.tgz", - "integrity": "sha512-WQEMkauUFLRWjhpHpiPGo1mfnLCvmeO840U+JZDB8Vn7eykQG6nklz0PxfBP91gneFBw6Ab11xuXcv17TU+Uig==", - "dependencies": { - "smooth-scroll-into-view-if-needed": "^2.0.2" - }, - "engines": { - "node": ">=18", - "npm": ">=9.5.0" - }, - "funding": { - "type": "patreon", - "url": "https://patreon.com/asmyshlyaev177" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" + "react": "^18.3.1" } }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" }, "node_modules/react-query": { "version": "3.39.3", "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.5.5", "broadcast-channel": "^3.4.1", @@ -7973,11 +8168,12 @@ } }, "node_modules/react-router": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", - "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.20.0" + "@remix-run/router": "1.23.2" }, "engines": { "node": ">=14.0.0" @@ -7987,12 +8183,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", - "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.20.0", - "react-router": "6.27.0" + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" }, "engines": { "node": ">=14.0.0" @@ -8002,52 +8199,21 @@ "react-dom": ">=16.8" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/recoil": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", - "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", - "dependencies": { - "hamt_plus": "1.0.2" - }, - "peerDependencies": { - "react": ">=16.13.1" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/reflect.getprototypeof": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", - "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "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.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.23.1", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" + "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" @@ -8060,13 +8226,15 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "dev": true, + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -8074,29 +8242,18 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "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.7", + "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": { @@ -8107,17 +8264,18 @@ } }, "node_modules/regexpu-core": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", - "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "dev": true, + "license": "MIT", "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.11.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -8127,15 +8285,17 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.2.tgz", - "integrity": "sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" @@ -8144,7 +8304,8 @@ "node_modules/remove-accents": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", - "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" }, "node_modules/require-directory": { "version": "2.1.1", @@ -8161,62 +8322,46 @@ "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/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "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-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/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/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==", + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "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" @@ -8227,6 +8372,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -8238,12 +8384,13 @@ } }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "license": "MIT", "peer": true, "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -8253,25 +8400,75 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@rollup/rollup-android-arm-eabi": "4.56.0", + "@rollup/rollup-android-arm64": "4.56.0", + "@rollup/rollup-darwin-arm64": "4.56.0", + "@rollup/rollup-darwin-x64": "4.56.0", + "@rollup/rollup-freebsd-arm64": "4.56.0", + "@rollup/rollup-freebsd-x64": "4.56.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", + "@rollup/rollup-linux-arm-musleabihf": "4.56.0", + "@rollup/rollup-linux-arm64-gnu": "4.56.0", + "@rollup/rollup-linux-arm64-musl": "4.56.0", + "@rollup/rollup-linux-loong64-gnu": "4.56.0", + "@rollup/rollup-linux-loong64-musl": "4.56.0", + "@rollup/rollup-linux-ppc64-gnu": "4.56.0", + "@rollup/rollup-linux-ppc64-musl": "4.56.0", + "@rollup/rollup-linux-riscv64-gnu": "4.56.0", + "@rollup/rollup-linux-riscv64-musl": "4.56.0", + "@rollup/rollup-linux-s390x-gnu": "4.56.0", + "@rollup/rollup-linux-x64-gnu": "4.56.0", + "@rollup/rollup-linux-x64-musl": "4.56.0", + "@rollup/rollup-openbsd-x64": "4.56.0", + "@rollup/rollup-openharmony-arm64": "4.56.0", + "@rollup/rollup-win32-arm64-msvc": "4.56.0", + "@rollup/rollup-win32-ia32-msvc": "4.56.0", + "@rollup/rollup-win32-x64-gnu": "4.56.0", + "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-visualizer": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.5.tgz", + "integrity": "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "open": "^8.0.0", + "picomatch": "^4.0.2", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "rolldown": "1.x || ^1.0.0-beta", + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -8291,29 +8488,22 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "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.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "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": { @@ -8341,17 +8531,36 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" + }, + "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.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "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-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -8360,29 +8569,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } }, - "node_modules/scroll-into-view-if-needed": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", - "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", - "dependencies": { - "compute-scroll-into-view": "^3.0.2" - } - }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/serialize-javascript": { @@ -8390,6 +8606,7 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -8399,6 +8616,7 @@ "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", @@ -8416,6 +8634,7 @@ "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", @@ -8426,50 +8645,19 @@ "node": ">= 0.4" } }, - "node_modules/sharp": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", - "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/sharp-ico": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/sharp-ico/-/sharp-ico-0.1.5.tgz", - "integrity": "sha512-a3jODQl82NPp1d5OYb0wY+oFaPk7AvyxipIowCHk7pBsZCWgbe0yAkU2OOXdoH0ENyANhyOQbs9xkAiRHcF02Q==", + "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": { - "decode-ico": "*", - "ico-endec": "*", - "sharp": "*" - } - }, - "node_modules/sharp/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, "node_modules/shebang-command": { @@ -8477,6 +8665,7 @@ "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" }, @@ -8489,30 +8678,40 @@ "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/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "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": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "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": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" @@ -8521,85 +8720,71 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/simple-concat": { + "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "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" - } - ] - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "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": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "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/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "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": { - "is-arrayish": "^0.3.1" + "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/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true + "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/smob": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", - "dev": true - }, - "node_modules/smooth-scroll-into-view-if-needed": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/smooth-scroll-into-view-if-needed/-/smooth-scroll-into-view-if-needed-2.0.2.tgz", - "integrity": "sha512-z54WzUSlM+xHHvJu3lMIsh+1d1kA4vaakcAtQvqzeGJ5Ffau7EKjpRrMHh1/OBo5zyU2h30ZYEt77vWmPHqg7Q==", - "dependencies": { - "scroll-into-view-if-needed": "^3.1.0" - } + "dev": true, + "license": "MIT" }, "node_modules/snake-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "dev": true, + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -8609,6 +8794,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -8617,6 +8803,7 @@ "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" } @@ -8626,6 +8813,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "devOptional": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -8636,6 +8824,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "devOptional": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -8645,32 +8834,40 @@ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true + "dev": true, + "license": "MIT" }, - "node_modules/streamx": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.2.tgz", - "integrity": "sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==", + "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": { - "fast-fifo": "^1.3.2", - "queue-tick": "^1.0.1", - "text-decoder": "^1.1.0" + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" }, - "optionalDependencies": { - "bare-events": "^2.2.0" + "engines": { + "node": ">= 0.4" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "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": { - "safe-buffer": "~5.2.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/string-width": { + "node_modules/string-width-cjs": { + "name": "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==", @@ -8686,23 +8883,25 @@ } }, "node_modules/string.prototype.matchall": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", - "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "regexp.prototype.flags": "^1.5.2", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -8716,21 +8915,26 @@ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, + "license": "MIT", "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "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.7", + "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.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8740,15 +8944,20 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "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.7", + "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" } @@ -8758,6 +8967,7 @@ "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", @@ -8775,6 +8985,7 @@ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", @@ -8789,6 +9000,21 @@ "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/strip-ansi-cjs": { + "name": "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" }, @@ -8801,6 +9027,7 @@ "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" } @@ -8810,6 +9037,7 @@ "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } @@ -8819,6 +9047,7 @@ "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" }, @@ -8829,13 +9058,15 @@ "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "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==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -8847,6 +9078,7 @@ "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==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -8858,27 +9090,29 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", "dev": true, + "license": "MIT", "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.3.1", + "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1", + "sax": "^1.4.1" }, "bin": { - "svgo": "bin/svgo" + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -8886,44 +9120,19 @@ } }, "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, + "license": "MIT", "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/tar-fs": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", - "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^2.1.1", - "bare-path": "^2.1.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "url": "https://opencollective.com/synckit" } }, "node_modules/temp-dir": { @@ -8931,6 +9140,7 @@ "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -8940,6 +9150,7 @@ "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", "dev": true, + "license": "MIT", "dependencies": { "is-stream": "^2.0.0", "temp-dir": "^2.0.0", @@ -8953,26 +9164,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tempy/node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/terser": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", - "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "devOptional": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -8987,39 +9187,33 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "devOptional": true - }, - "node_modules/text-decoder": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", - "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==", - "dev": true + "devOptional": true, + "license": "MIT" }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", - "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, + "license": "MIT", "dependencies": { - "fdir": "^6.4.2", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/to-data-view": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-1.1.0.tgz", - "integrity": "sha512-1eAdufMg6mwgmlojAx3QeMnzB/BTVp7Tbndi3U7ftcT2zCZadjxkkmLmd97zmaxWi+sgGcgWrokmpEoy0Dn0vQ==", - "dev": true - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -9030,47 +9224,27 @@ "node": ">=4" } }, - "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, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", "dev": true, + "license": "MIT", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tsconfig-paths": { @@ -9078,6 +9252,7 @@ "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", @@ -9085,343 +9260,142 @@ "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, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==" - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.11.0.tgz", - "integrity": "sha512-cBRGnW3FSlxaYwU8KfAewxFK5uzeOAp0l2KebIlPDOT5olVi65KDG/yjBooPBG0kGW/HLkoz1c/iuBFehcS3IA==", - "dev": true, - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.11.0", - "@typescript-eslint/parser": "8.11.0", - "@typescript-eslint/utils": "8.11.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz", - "integrity": "sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==", + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/type-utils": "8.11.0", - "@typescript-eslint/utils": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "prelude-ls": "^1.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 0.8.0" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.11.0.tgz", - "integrity": "sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==", + "node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", "dev": true, - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", - "debug": "^4.3.4" - }, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.11.0.tgz", - "integrity": "sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==", + "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": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 0.4" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.11.0.tgz", - "integrity": "sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==", + "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": { - "@typescript-eslint/typescript-estree": "8.11.0", - "@typescript-eslint/utils": "8.11.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "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": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", - "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", + "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": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", - "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", + "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": { - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/visitor-keys": "8.11.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "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": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.11.0.tgz", - "integrity": "sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==", + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.11.0", - "@typescript-eslint/types": "8.11.0", - "@typescript-eslint/typescript-estree": "8.11.0" + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "node": ">=14.17" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", - "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", + "node_modules/typescript-eslint": { + "version": "8.53.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", + "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.11.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/eslint-plugin": "8.53.1", + "@typescript-eslint/parser": "8.53.1", + "@typescript-eslint/typescript-estree": "8.53.1", + "@typescript-eslint/utils": "8.53.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9429,53 +9403,35 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "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-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unconfig": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.3.13.tgz", - "integrity": "sha512-N9Ph5NC4+sqtcOjPfHrRcHekBCadCXWTBzp2VYYbySOHW0PfD9XLCeXshTXjkPYwLrBr9AtSeU0CZmkYECJhng==", - "dev": true, - "dependencies": { - "@antfu/utils": "^0.7.7", - "defu": "^6.1.4", - "jiti": "^1.21.0" + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "devOptional": true, "license": "MIT" }, @@ -9484,6 +9440,7 @@ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -9493,6 +9450,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "dev": true, + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -9502,19 +9460,21 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -9524,6 +9484,7 @@ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", "dev": true, + "license": "MIT", "dependencies": { "crypto-random-string": "^2.0.0" }, @@ -9536,6 +9497,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -9544,6 +9506,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.6.2", "detect-node": "^2.0.4" @@ -9554,15 +9517,16 @@ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true, + "license": "MIT", "engines": { "node": ">=4", "yarn": "*" } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -9578,9 +9542,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -9594,20 +9559,16 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/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 - }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "license": "MIT", "peer": true, "dependencies": { "esbuild": "^0.21.3", @@ -9663,17 +9624,12 @@ } } }, - "node_modules/vite-plugin": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/vite-plugin/-/vite-plugin-0.0.0.tgz", - "integrity": "sha512-6aFl/G7X41T3DCpNDQcji2TeFXhQ/41JfhsBoUqhR/86bWSrqwOc05RJpDKrVysGMKbdeHoJ6F9UkBL2iwGiLw==", - "dev": true - }, "node_modules/vite-plugin-pwa": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.21.0.tgz", - "integrity": "sha512-gnDE5sN2hdxA4vTl0pe6PCTPXqChk175jH8dZVVTBjFhWarZZoXaAdoTIKCIa8Zbx94sC0CnCOyERBWpxvry+g==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.21.2.tgz", + "integrity": "sha512-vFhH6Waw8itNu37hWUJxL50q+CBbNcMVzsKaYHQVrfxTt3ihk3PeLO22SbiP1UNWzcEPaTQv+YVxe4G0KOjAkg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", @@ -9689,7 +9645,7 @@ }, "peerDependencies": { "@vite-pwa/assets-generator": "^0.2.6", - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", "workbox-build": "^7.3.0", "workbox-window": "^7.3.0" }, @@ -9703,18 +9659,19 @@ "version": "0.9.6", "resolved": "https://registry.npmjs.org/vite-plugin-radar/-/vite-plugin-radar-0.9.6.tgz", "integrity": "sha512-tLvUt7+iZznxYa8GmCrZBV3Q0fLQApsyg9EIJgaen8DjGky3vFIq9KoDWAoVMs9FZ5qbsSBb3YfSvoqwVV+5xw==", + "license": "MIT", "peerDependencies": { "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "node_modules/vite-plugin-svgr": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz", - "integrity": "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.5.0.tgz", + "integrity": "sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==", "dev": true, "license": "MIT", "dependencies": { - "@rollup/pluginutils": "^5.1.3", + "@rollup/pluginutils": "^5.2.0", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0" }, @@ -9723,26 +9680,28 @@ } }, "node_modules/vite-plugin-wasm": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.4.1.tgz", - "integrity": "sha512-ja3nSo2UCkVeitltJGkS3pfQHAanHv/DqGatdI39ja6McgABlpsZ5hVgl6wuR8Qx5etY3T5qgDQhOWzc5RReZA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.5.0.tgz", + "integrity": "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==", "dev": true, "license": "MIT", "peerDependencies": { - "vite": "^2 || ^3 || ^4 || ^5 || ^6" + "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7" } }, "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", "dev": true, + "license": "MIT", "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -9754,6 +9713,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -9765,39 +9725,45 @@ } }, "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "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.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "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.1.4", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", - "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "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.0.5", - "is-finalizationregistry": "^1.0.2", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", + "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", + "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -9811,6 +9777,7 @@ "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", @@ -9825,15 +9792,18 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", + "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": { @@ -9848,34 +9818,38 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/workbox-background-sync": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.3.0.tgz", - "integrity": "sha512-PCSk3eK7Mxeuyatb22pcSx9dlgWNv3+M8PqPaYDokks8Y5/FX4soaOqj3yhAZr5k6Q5JWTOMYgaJBpbw11G9Eg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz", + "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==", "dev": true, + "license": "MIT", "dependencies": { "idb": "^7.0.1", - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-broadcast-update": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.3.0.tgz", - "integrity": "sha512-T9/F5VEdJVhwmrIAE+E/kq5at2OY6+OXXgOWQevnubal6sO92Gjo24v6dCVwQiclAF5NS3hlmsifRrpQzZCdUA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz", + "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-build": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.3.0.tgz", - "integrity": "sha512-JGL6vZTPlxnlqZRhR/K/msqg3wKP+m0wfEUVosK7gsYzSgeIxvZLi1ViJJzVL7CEeI8r7rGFV973RiEqkP3lWQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz", + "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==", "dev": true, + "license": "MIT", "dependencies": { "@apideck/better-ajv-errors": "^0.3.1", "@babel/core": "^7.24.4", @@ -9890,33 +9864,33 @@ "common-tags": "^1.8.0", "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^9.0.1", - "glob": "^7.1.6", + "glob": "^11.0.1", "lodash": "^4.17.20", "pretty-bytes": "^5.3.0", - "rollup": "^2.43.1", + "rollup": "^2.79.2", "source-map": "^0.8.0-beta.0", "stringify-object": "^3.3.0", "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", - "workbox-background-sync": "7.3.0", - "workbox-broadcast-update": "7.3.0", - "workbox-cacheable-response": "7.3.0", - "workbox-core": "7.3.0", - "workbox-expiration": "7.3.0", - "workbox-google-analytics": "7.3.0", - "workbox-navigation-preload": "7.3.0", - "workbox-precaching": "7.3.0", - "workbox-range-requests": "7.3.0", - "workbox-recipes": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0", - "workbox-streams": "7.3.0", - "workbox-sw": "7.3.0", - "workbox-window": "7.3.0" - }, - "engines": { - "node": ">=16.0.0" + "workbox-background-sync": "7.4.0", + "workbox-broadcast-update": "7.4.0", + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-google-analytics": "7.4.0", + "workbox-navigation-preload": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-range-requests": "7.4.0", + "workbox-recipes": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0", + "workbox-streams": "7.4.0", + "workbox-sw": "7.4.0", + "workbox-window": "7.4.0" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { @@ -9924,6 +9898,7 @@ "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", "dev": true, + "license": "MIT", "dependencies": { "json-schema": "^0.4.0", "jsonpointer": "^5.0.0", @@ -9941,6 +9916,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.10.4", "@rollup/pluginutils": "^3.1.0" @@ -9964,6 +9940,7 @@ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^3.1.0", "magic-string": "^0.25.7" @@ -9977,6 +9954,7 @@ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", @@ -9993,13 +9971,15 @@ "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/workbox-build/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", "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", @@ -10016,19 +9996,62 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/workbox-build/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 + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/workbox-build/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" }, @@ -10041,6 +10064,7 @@ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -10053,6 +10077,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "dev": true, + "license": "MIT", "peer": true, "bin": { "rollup": "dist/bin/rollup" @@ -10068,7 +10093,9 @@ "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "whatwg-url": "^7.0.0" }, @@ -10077,127 +10104,140 @@ } }, "node_modules/workbox-cacheable-response": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.3.0.tgz", - "integrity": "sha512-eAFERIg6J2LuyELhLlmeRcJFa5e16Mj8kL2yCDbhWE+HUun9skRQrGIFVUagqWj4DMaaPSMWfAolM7XZZxNmxA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz", + "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-core": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.3.0.tgz", - "integrity": "sha512-Z+mYrErfh4t3zi7NVTvOuACB0A/jA3bgxUN3PwtAVHvfEsZxV9Iju580VEETug3zYJRc0Dmii/aixI/Uxj8fmw==", - "dev": true + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz", + "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==", + "dev": true, + "license": "MIT" }, "node_modules/workbox-expiration": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.3.0.tgz", - "integrity": "sha512-lpnSSLp2BM+K6bgFCWc5bS1LR5pAwDWbcKt1iL87/eTSJRdLdAwGQznZE+1czLgn/X05YChsrEegTNxjM067vQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz", + "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==", "dev": true, + "license": "MIT", "dependencies": { "idb": "^7.0.1", - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-google-analytics": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.3.0.tgz", - "integrity": "sha512-ii/tSfFdhjLHZ2BrYgFNTrb/yk04pw2hasgbM70jpZfLk0vdJAXgaiMAWsoE+wfJDNWoZmBYY0hMVI0v5wWDbg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz", + "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-background-sync": "7.3.0", - "workbox-core": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0" + "workbox-background-sync": "7.4.0", + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" } }, "node_modules/workbox-navigation-preload": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.3.0.tgz", - "integrity": "sha512-fTJzogmFaTv4bShZ6aA7Bfj4Cewaq5rp30qcxl2iYM45YD79rKIhvzNHiFj1P+u5ZZldroqhASXwwoyusnr2cg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz", + "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-precaching": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.3.0.tgz", - "integrity": "sha512-ckp/3t0msgXclVAYaNndAGeAoWQUv7Rwc4fdhWL69CCAb2UHo3Cef0KIUctqfQj1p8h6aGyz3w8Cy3Ihq9OmIw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz", + "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0" + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" } }, "node_modules/workbox-range-requests": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.3.0.tgz", - "integrity": "sha512-EyFmM1KpDzzAouNF3+EWa15yDEenwxoeXu9bgxOEYnFfCxns7eAxA9WSSaVd8kujFFt3eIbShNqa4hLQNFvmVQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz", + "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-recipes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.3.0.tgz", - "integrity": "sha512-BJro/MpuW35I/zjZQBcoxsctgeB+kyb2JAP5EB3EYzePg8wDGoQuUdyYQS+CheTb+GhqJeWmVs3QxLI8EBP1sg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz", + "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-cacheable-response": "7.3.0", - "workbox-core": "7.3.0", - "workbox-expiration": "7.3.0", - "workbox-precaching": "7.3.0", - "workbox-routing": "7.3.0", - "workbox-strategies": "7.3.0" + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" } }, "node_modules/workbox-routing": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.3.0.tgz", - "integrity": "sha512-ZUlysUVn5ZUzMOmQN3bqu+gK98vNfgX/gSTZ127izJg/pMMy4LryAthnYtjuqcjkN4HEAx1mdgxNiKJMZQM76A==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz", + "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-strategies": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.3.0.tgz", - "integrity": "sha512-tmZydug+qzDFATwX7QiEL5Hdf7FrkhjaF9db1CbB39sDmEZJg3l9ayDvPxy8Y18C3Y66Nrr9kkN1f/RlkDgllg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz", + "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/workbox-streams": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.3.0.tgz", - "integrity": "sha512-SZnXucyg8x2Y61VGtDjKPO5EgPUG5NDn/v86WYHX+9ZqvAsGOytP0Jxp1bl663YUuMoXSAtsGLL+byHzEuMRpw==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz", + "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==", "dev": true, + "license": "MIT", "dependencies": { - "workbox-core": "7.3.0", - "workbox-routing": "7.3.0" + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0" } }, "node_modules/workbox-sw": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.3.0.tgz", - "integrity": "sha512-aCUyoAZU9IZtH05mn0ACUpyHzPs0lMeJimAYkQkBsOWiqaJLgusfDCR+yllkPkFRxWpZKF8vSvgHYeG7LwhlmA==", - "dev": true + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz", + "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==", + "dev": true, + "license": "MIT" }, "node_modules/workbox-window": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.3.0.tgz", - "integrity": "sha512-qW8PDy16OV1UBaUNGlTVcepzrlzyzNW/ZJvFQQs2j2TzGsg6IKjcpZC1RSquqQnTOafl5pCj5bGfAHlCjOOjdA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz", + "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==", "dev": true, + "license": "MIT", "dependencies": { "@types/trusted-types": "^2.0.2", - "workbox-core": "7.3.0" + "workbox-core": "7.4.0" } }, "node_modules/wrap-ansi": { @@ -10218,10 +10258,30 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/y18n": { "version": "5.0.8", @@ -10237,12 +10297,14 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", "engines": { "node": ">= 6" } @@ -10281,6 +10343,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index bb425bc0..fec34999 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,12 @@ "dev": "vite --host", "build": "tsc -b && vite build", "lint": "eslint .", + "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css}\"", + "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css}\"", "preview": "vite preview", "predeploy": "npm run build", "deploy": "gh-pages -d build", "gen-pwa": "pwa-assets-generator --preset minimal public/appLogo.svg", - "asbuild:debug": "asc assembly/index.ts --target debug", - "asbuild:release": "asc assembly/index.ts --target release", - "asbuild": "npm run asbuild:debug && npm run asbuild:release", "test": "node tests", "start": "npx serve ." }, @@ -21,31 +20,23 @@ "dependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@types/react-modal": "^3.16.3", - "framer-motion": "^11.11.17", - "prettier-plugin-emotion-order": "^1.0.1", + "framer-motion": "^11.18.2", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-horizontal-scrolling-menu": "^8.2.0", "react-query": "^3.39.3", - "react-router-dom": "^6.27.0", - "recoil": "^0.7.7", + "react-router": "^6.30.3", + "react-router-dom": "^6.30.3", "vite-plugin-radar": "^0.9.6" }, "devDependencies": { "@eslint/js": "^9.13.0", - "@svgr/rollup": "^8.1.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "@types/emscripten": "^1.39.13", "@types/node": "^22.10.1", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^8.12.2", "@typescript-eslint/parser": "^8.12.2", - "@vite-pwa/assets-generator": "^0.2.6", "@vitejs/plugin-react-swc": "^3.7.1", - "assemblyscript": "^0.27.31", - "concurrently": "^9.1.0", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.31.0", @@ -59,12 +50,11 @@ "prettier": "^3.3.3", "prettier-plugin-css-order": "^2.1.2", "prettier-plugin-emotion-order": "^1.1.0", - "prettier-plugin-style-order": "^0.2.2", - "react-error-boundary": "^4.1.2", + "rollup-plugin-visualizer": "^6.0.5", + "svgo": "^4.0.0", "typescript": "^5.7.2", "typescript-eslint": "^8.11.0", "vite": "^5.4.11", - "vite-plugin": "^0.0.0", "vite-plugin-pwa": "^0.21.0", "vite-plugin-svgr": "^4.3.0", "vite-plugin-wasm": "^3.4.1" diff --git a/public/.well-known/apple-app-site-association b/public/.well-known/apple-app-site-association new file mode 100644 index 00000000..d89b411c --- /dev/null +++ b/public/.well-known/apple-app-site-association @@ -0,0 +1,15 @@ +{ + "applinks": { + "apps": [], + "details": [ + { + "appID": "C9TY6BF2XS.com.durumi99.humanzipyoapp", + "paths": [ + "NOT /login/oauth2/code/*", + "/*" + ] + } + ] + } +} + diff --git a/public/.well-known/assetlinks.json b/public/.well-known/assetlinks.json new file mode 100644 index 00000000..3e3fe4ae --- /dev/null +++ b/public/.well-known/assetlinks.json @@ -0,0 +1,16 @@ +[ + { + "relation": [ + "delegate_permission/common.handle_all_urls", + "delegate_permission/common.get_login_creds" + ], + "target": { + "namespace": "android_app", + "package_name": "com.durumi99.humanzipyoapp", + "sha256_cert_fingerprints": [ + "FAC61745DC0903786FB9EDE62A962B399F7348F0BB6F899B8332667591033B9C", + "80:18:37:AD:35:2A:7B:6B:82:F0:84:1F:29:8A:8A:2F:F9:A8:E5:06:E9:28:F6:43:C2:4C:43:A7:2F:D5:BC:35" + ] + } + } +] diff --git a/git b/public/404.html similarity index 100% rename from git rename to public/404.html diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 00000000..8446b831 --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,26 @@ +{ + "name": "인간지표 : 주식투자심리도우미", + "short_name": "인간지표", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#3457FD", + "icons": [ + { + "src": "/pwa-64x64.png", + "sizes": "64x64", + "type": "image/png" + }, + { + "src": "/pwa-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/pwa-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ] +} diff --git a/src/App.css b/src/App.css index cd3aafc8..1730b6bc 100644 --- a/src/App.css +++ b/src/App.css @@ -1,3 +1,7 @@ body { font-family: 'Pretendard', sans-serif; } + +input { + font-family: 'Pretendard', sans-serif; +} diff --git a/src/App.tsx b/src/App.tsx index 1c2b5f07..6d78fefb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,14 @@ +// import { useEffect } from 'react'; import { RouterProvider } from 'react-router-dom'; +// import { initOpenDays } from '@ts/OpenDays'; import './App.css'; import { router } from './router'; function App() { + // useEffect(() => { + // (async () => await initOpenDays())(); + // }, []); + return ; } diff --git a/src/assets/PWA/Android/AddToHome.png b/src/assets/PWA/Android/AddToHome.png new file mode 100644 index 00000000..cdf9b788 Binary files /dev/null and b/src/assets/PWA/Android/AddToHome.png differ diff --git a/src/assets/PWA/Android/AddToHome.svg b/src/assets/PWA/Android/AddToHome.svg deleted file mode 100644 index 3dd12d75..00000000 --- a/src/assets/PWA/Android/AddToHome.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/PWA/Android/ShareButton.png b/src/assets/PWA/Android/ShareButton.png new file mode 100644 index 00000000..c3f6b8d3 Binary files /dev/null and b/src/assets/PWA/Android/ShareButton.png differ diff --git a/src/assets/PWA/Android/ShareButton.svg b/src/assets/PWA/Android/ShareButton.svg deleted file mode 100644 index 2435b303..00000000 --- a/src/assets/PWA/Android/ShareButton.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/PWA/IOS/AddToHome.png b/src/assets/PWA/IOS/AddToHome.png new file mode 100644 index 00000000..8dcb8968 Binary files /dev/null and b/src/assets/PWA/IOS/AddToHome.png differ diff --git a/src/assets/PWA/IOS/AddToHome.svg b/src/assets/PWA/IOS/AddToHome.svg deleted file mode 100644 index 3e952afb..00000000 --- a/src/assets/PWA/IOS/AddToHome.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/PWA/IOS/ShareButton.png b/src/assets/PWA/IOS/ShareButton.png new file mode 100644 index 00000000..8cf81d54 Binary files /dev/null and b/src/assets/PWA/IOS/ShareButton.png differ diff --git a/src/assets/PWA/IOS/ShareButton.svg b/src/assets/PWA/IOS/ShareButton.svg deleted file mode 100644 index 4b9b3e30..00000000 --- a/src/assets/PWA/IOS/ShareButton.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/PWA/RunApp.png b/src/assets/PWA/RunApp.png new file mode 100644 index 00000000..555c40fd Binary files /dev/null and b/src/assets/PWA/RunApp.png differ diff --git a/src/assets/PWA/RunApp.svg b/src/assets/PWA/RunApp.svg deleted file mode 100644 index 20990c54..00000000 --- a/src/assets/PWA/RunApp.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/README.md b/src/assets/README.md new file mode 100644 index 00000000..1f2ebd65 --- /dev/null +++ b/src/assets/README.md @@ -0,0 +1,4 @@ +https://brunch.co.kr/@sunking1126/4 + +1. snake_case +2. {icon name}_{ciecle}_{fill}.svg diff --git a/src/assets/alarm.svg b/src/assets/alarm.svg new file mode 100644 index 00000000..64996995 --- /dev/null +++ b/src/assets/alarm.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/appDownload/appDownloadAlarm.svg b/src/assets/appDownload/appDownloadAlarm.svg new file mode 100644 index 00000000..896ca447 --- /dev/null +++ b/src/assets/appDownload/appDownloadAlarm.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/appDownload/appDownloadLightning.svg b/src/assets/appDownload/appDownloadLightning.svg new file mode 100644 index 00000000..1fe517d5 --- /dev/null +++ b/src/assets/appDownload/appDownloadLightning.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/appDownload/appDownloadPhone.svg b/src/assets/appDownload/appDownloadPhone.svg new file mode 100644 index 00000000..45688408 --- /dev/null +++ b/src/assets/appDownload/appDownloadPhone.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/appleLogin.png b/src/assets/appleLogin.png new file mode 100644 index 00000000..c6f10ba0 Binary files /dev/null and b/src/assets/appleLogin.png differ diff --git a/src/assets/arrowLeft.svg b/src/assets/arrowLeft.svg new file mode 100644 index 00000000..4ac59567 --- /dev/null +++ b/src/assets/arrowLeft.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/backLogo.svg b/src/assets/backLogo.svg new file mode 100644 index 00000000..a0506264 --- /dev/null +++ b/src/assets/backLogo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/background.svg b/src/assets/background.svg new file mode 100644 index 00000000..21927bd3 --- /dev/null +++ b/src/assets/background.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/blueAlert.svg b/src/assets/blueAlert.svg new file mode 100644 index 00000000..914866ed --- /dev/null +++ b/src/assets/blueAlert.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/bottomNav/favorites.svg b/src/assets/bottomNav/favorites.svg new file mode 100644 index 00000000..181523c3 --- /dev/null +++ b/src/assets/bottomNav/favorites.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/bottomNav/home.svg b/src/assets/bottomNav/home.svg new file mode 100644 index 00000000..5a7ede81 --- /dev/null +++ b/src/assets/bottomNav/home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/bottomNav/lab.svg b/src/assets/bottomNav/lab.svg new file mode 100644 index 00000000..e2a8d844 --- /dev/null +++ b/src/assets/bottomNav/lab.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/bottomNav/myPage.svg b/src/assets/bottomNav/myPage.svg new file mode 100644 index 00000000..1fb42afc --- /dev/null +++ b/src/assets/bottomNav/myPage.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/bottomNav/shortView.svg b/src/assets/bottomNav/shortView.svg new file mode 100644 index 00000000..b6e74885 --- /dev/null +++ b/src/assets/bottomNav/shortView.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/check.svg b/src/assets/check.svg new file mode 100644 index 00000000..df5410d3 --- /dev/null +++ b/src/assets/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/checkCircle.svg b/src/assets/checkCircle.svg new file mode 100644 index 00000000..45bcfc57 --- /dev/null +++ b/src/assets/checkCircle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/close.svg b/src/assets/close.svg new file mode 100644 index 00000000..96bb7707 --- /dev/null +++ b/src/assets/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/default-stock-image.png b/src/assets/default-stock-image.png new file mode 100644 index 00000000..9aafbd07 Binary files /dev/null and b/src/assets/default-stock-image.png differ diff --git a/src/assets/design/alarmExample.png b/src/assets/design/alarmExample.png new file mode 100644 index 00000000..b13194a3 Binary files /dev/null and b/src/assets/design/alarmExample.png differ diff --git a/src/assets/design/antVoice.png b/src/assets/design/antVoice.png new file mode 100644 index 00000000..20dbab33 Binary files /dev/null and b/src/assets/design/antVoice.png differ diff --git a/src/assets/design/callout/callout_tail.svg b/src/assets/design/callout/callout_tail.svg new file mode 100644 index 00000000..0169b370 --- /dev/null +++ b/src/assets/design/callout/callout_tail.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/design/card/card.svg b/src/assets/design/card/card.svg new file mode 100644 index 00000000..8c6954c1 --- /dev/null +++ b/src/assets/design/card/card.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/design/common_rule.png b/src/assets/design/common_rule.png new file mode 100644 index 00000000..d83b517d Binary files /dev/null and b/src/assets/design/common_rule.png differ diff --git a/src/assets/design/defaultAlarm.png b/src/assets/design/defaultAlarm.png new file mode 100644 index 00000000..a6742710 Binary files /dev/null and b/src/assets/design/defaultAlarm.png differ diff --git a/src/assets/design/defaultAlarm2.png b/src/assets/design/defaultAlarm2.png new file mode 100644 index 00000000..35aed438 Binary files /dev/null and b/src/assets/design/defaultAlarm2.png differ diff --git a/src/assets/design/defaultStock.png b/src/assets/design/defaultStock.png new file mode 100644 index 00000000..9aafbd07 Binary files /dev/null and b/src/assets/design/defaultStock.png differ diff --git a/src/assets/design/shortViewApp.png b/src/assets/design/shortViewApp.png new file mode 100644 index 00000000..15370c33 Binary files /dev/null and b/src/assets/design/shortViewApp.png differ diff --git a/src/assets/design/speechBubble.svg b/src/assets/design/speechBubble.svg new file mode 100644 index 00000000..62f26678 --- /dev/null +++ b/src/assets/design/speechBubble.svg @@ -0,0 +1,12 @@ + +
+ + + + + + + + + +
diff --git a/src/assets/edit_circle.svg b/src/assets/edit_circle.svg new file mode 100644 index 00000000..3ac4ae06 --- /dev/null +++ b/src/assets/edit_circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/flags/korea.png b/src/assets/flags/korea.png new file mode 100644 index 00000000..09fa7f58 Binary files /dev/null and b/src/assets/flags/korea.png differ diff --git a/src/assets/flags/oversea.png b/src/assets/flags/oversea.png new file mode 100644 index 00000000..aaab8c2a Binary files /dev/null and b/src/assets/flags/oversea.png differ diff --git a/src/assets/footer/footer_term.svg b/src/assets/footer/footer_term.svg new file mode 100644 index 00000000..cf2b8ff0 --- /dev/null +++ b/src/assets/footer/footer_term.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/googleLogin.png b/src/assets/googleLogin.png new file mode 100644 index 00000000..58ad90bd Binary files /dev/null and b/src/assets/googleLogin.png differ diff --git a/src/assets/heart.svg b/src/assets/heart.svg new file mode 100644 index 00000000..c027e60e --- /dev/null +++ b/src/assets/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/addStock.svg b/src/assets/icons/addStock.svg new file mode 100644 index 00000000..d78cfef1 --- /dev/null +++ b/src/assets/icons/addStock.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/aim.svg b/src/assets/icons/aim.svg new file mode 100644 index 00000000..9011fad2 --- /dev/null +++ b/src/assets/icons/aim.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/alarm.svg b/src/assets/icons/alarm.svg new file mode 100644 index 00000000..983f1d76 --- /dev/null +++ b/src/assets/icons/alarm.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/alert.svg b/src/assets/icons/alert.svg new file mode 100644 index 00000000..19b55863 --- /dev/null +++ b/src/assets/icons/alert.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/arrowDropUp.svg b/src/assets/icons/arrowDropUp.svg new file mode 100644 index 00000000..67d0872f --- /dev/null +++ b/src/assets/icons/arrowDropUp.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/arrowLeft.svg b/src/assets/icons/arrowLeft.svg new file mode 100644 index 00000000..2cb22ff6 --- /dev/null +++ b/src/assets/icons/arrowLeft.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/arrowUp.svg b/src/assets/icons/arrowUp.svg new file mode 100644 index 00000000..a100acd3 --- /dev/null +++ b/src/assets/icons/arrowUp.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/bell.svg b/src/assets/icons/bell.svg new file mode 100644 index 00000000..05dd2b5f --- /dev/null +++ b/src/assets/icons/bell.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/assets/icons/cancel.svg b/src/assets/icons/cancel.svg index bfbe9125..45e9f74b 100644 --- a/src/assets/icons/cancel.svg +++ b/src/assets/icons/cancel.svg @@ -1,5 +1,5 @@ - - + + diff --git a/src/assets/icons/check.svg b/src/assets/icons/check.svg new file mode 100644 index 00000000..47b3969d --- /dev/null +++ b/src/assets/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/checkCircle.svg b/src/assets/icons/checkCircle.svg new file mode 100644 index 00000000..76045ba7 --- /dev/null +++ b/src/assets/icons/checkCircle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/chevronDown.svg b/src/assets/icons/chevronDown.svg new file mode 100644 index 00000000..31657796 --- /dev/null +++ b/src/assets/icons/chevronDown.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/chevronLeft.svg b/src/assets/icons/chevronLeft.svg new file mode 100644 index 00000000..c3089976 --- /dev/null +++ b/src/assets/icons/chevronLeft.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/chevronLeftNarrow.svg b/src/assets/icons/chevronLeftNarrow.svg new file mode 100644 index 00000000..a2849a04 --- /dev/null +++ b/src/assets/icons/chevronLeftNarrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/chevronUp.svg b/src/assets/icons/chevronUp.svg new file mode 100644 index 00000000..63abaf3f --- /dev/null +++ b/src/assets/icons/chevronUp.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/clock.svg b/src/assets/icons/clock.svg new file mode 100644 index 00000000..88591a7b --- /dev/null +++ b/src/assets/icons/clock.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/cross.svg b/src/assets/icons/cross.svg new file mode 100644 index 00000000..e50546dd --- /dev/null +++ b/src/assets/icons/cross.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/detail.svg b/src/assets/icons/detail.svg new file mode 100644 index 00000000..05993bd8 --- /dev/null +++ b/src/assets/icons/detail.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/down.svg b/src/assets/icons/down.svg index 019f2e62..e8de41aa 100644 --- a/src/assets/icons/down.svg +++ b/src/assets/icons/down.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/assets/icons/downCaret.svg b/src/assets/icons/downCaret.svg new file mode 100644 index 00000000..80b86fa7 --- /dev/null +++ b/src/assets/icons/downCaret.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/edit.svg b/src/assets/icons/edit.svg new file mode 100644 index 00000000..86fb5251 --- /dev/null +++ b/src/assets/icons/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/exclamation_mark_circle.svg b/src/assets/icons/exclamation_mark_circle.svg new file mode 100644 index 00000000..629af33f --- /dev/null +++ b/src/assets/icons/exclamation_mark_circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/heart.svg b/src/assets/icons/heart.svg new file mode 100644 index 00000000..f91993d0 --- /dev/null +++ b/src/assets/icons/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/info.svg b/src/assets/icons/info.svg new file mode 100644 index 00000000..f554b348 --- /dev/null +++ b/src/assets/icons/info.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/magnifier.svg b/src/assets/icons/magnifier.svg new file mode 100644 index 00000000..12359eb7 --- /dev/null +++ b/src/assets/icons/magnifier.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/money.svg b/src/assets/icons/money.svg new file mode 100644 index 00000000..8b243e87 --- /dev/null +++ b/src/assets/icons/money.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/pencil.svg b/src/assets/icons/pencil.svg new file mode 100644 index 00000000..f87925fc --- /dev/null +++ b/src/assets/icons/pencil.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/plus.svg b/src/assets/icons/plus.svg new file mode 100644 index 00000000..410fc02d --- /dev/null +++ b/src/assets/icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/purchaseCheck.svg b/src/assets/icons/purchaseCheck.svg new file mode 100644 index 00000000..076dce43 --- /dev/null +++ b/src/assets/icons/purchaseCheck.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/questionMark.svg b/src/assets/icons/questionMark.svg new file mode 100644 index 00000000..ebf452a4 --- /dev/null +++ b/src/assets/icons/questionMark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/question_mark_circle.svg b/src/assets/icons/question_mark_circle.svg new file mode 100644 index 00000000..b769fc90 --- /dev/null +++ b/src/assets/icons/question_mark_circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/question_mark_circle_fill.svg b/src/assets/icons/question_mark_circle_fill.svg new file mode 100644 index 00000000..0ce403e7 --- /dev/null +++ b/src/assets/icons/question_mark_circle_fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/rightArrow.svg b/src/assets/icons/rightArrow.svg index 175fda2a..95bbea30 100644 --- a/src/assets/icons/rightArrow.svg +++ b/src/assets/icons/rightArrow.svg @@ -1,3 +1,3 @@ - + diff --git a/src/assets/icons/search.svg b/src/assets/icons/search.svg index 3573a187..b803a26b 100644 --- a/src/assets/icons/search.svg +++ b/src/assets/icons/search.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/src/assets/icons/shortview/chevronRight.svg b/src/assets/icons/shortview/chevronRight.svg new file mode 100644 index 00000000..01b40332 --- /dev/null +++ b/src/assets/icons/shortview/chevronRight.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/toast/bell.svg b/src/assets/icons/toast/bell.svg new file mode 100644 index 00000000..8be04040 --- /dev/null +++ b/src/assets/icons/toast/bell.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/toast/bell_cross.svg b/src/assets/icons/toast/bell_cross.svg new file mode 100644 index 00000000..ab7b4f1b --- /dev/null +++ b/src/assets/icons/toast/bell_cross.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/toast/heart.svg b/src/assets/icons/toast/heart.svg new file mode 100644 index 00000000..c8665314 --- /dev/null +++ b/src/assets/icons/toast/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/uncheck.svg b/src/assets/icons/uncheck.svg new file mode 100644 index 00000000..581378f9 --- /dev/null +++ b/src/assets/icons/uncheck.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/upCaret.svg b/src/assets/icons/upCaret.svg new file mode 100644 index 00000000..e4ed48e1 --- /dev/null +++ b/src/assets/icons/upCaret.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/warning.svg b/src/assets/icons/warning.svg new file mode 100644 index 00000000..97ea2ae4 --- /dev/null +++ b/src/assets/icons/warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/whiteQuestionMark.svg b/src/assets/icons/whiteQuestionMark.svg new file mode 100644 index 00000000..a770f2ca --- /dev/null +++ b/src/assets/icons/whiteQuestionMark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/instagram.svg b/src/assets/instagram.svg new file mode 100644 index 00000000..4b5bb04e --- /dev/null +++ b/src/assets/instagram.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/kakaoLogin.png b/src/assets/kakaoLogin.png new file mode 100644 index 00000000..5994fef3 Binary files /dev/null and b/src/assets/kakaoLogin.png differ diff --git a/src/assets/lab.svg b/src/assets/lab.svg new file mode 100644 index 00000000..2fd0014b --- /dev/null +++ b/src/assets/lab.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/lab/checkCircleSelected.svg b/src/assets/lab/checkCircleSelected.svg new file mode 100644 index 00000000..d94e9453 --- /dev/null +++ b/src/assets/lab/checkCircleSelected.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/lab/checkCircleUnelected.svg b/src/assets/lab/checkCircleUnelected.svg new file mode 100644 index 00000000..75ed618c --- /dev/null +++ b/src/assets/lab/checkCircleUnelected.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/lab/lab-intro-1.png b/src/assets/lab/lab-intro-1.png new file mode 100644 index 00000000..f008df8e Binary files /dev/null and b/src/assets/lab/lab-intro-1.png differ diff --git a/src/assets/lab/lab-intro-2.png b/src/assets/lab/lab-intro-2.png new file mode 100644 index 00000000..01358f96 Binary files /dev/null and b/src/assets/lab/lab-intro-2.png differ diff --git a/src/assets/lab/lab-intro-3.png b/src/assets/lab/lab-intro-3.png new file mode 100644 index 00000000..11ac08b4 Binary files /dev/null and b/src/assets/lab/lab-intro-3.png differ diff --git a/src/assets/lab/lab-result.png b/src/assets/lab/lab-result.png new file mode 100644 index 00000000..c7862c81 Binary files /dev/null and b/src/assets/lab/lab-result.png differ diff --git a/src/assets/lab/labTutorial1.png b/src/assets/lab/labTutorial1.png new file mode 100644 index 00000000..f008df8e Binary files /dev/null and b/src/assets/lab/labTutorial1.png differ diff --git a/src/assets/lab/labTutorial2.png b/src/assets/lab/labTutorial2.png new file mode 100644 index 00000000..01358f96 Binary files /dev/null and b/src/assets/lab/labTutorial2.png differ diff --git a/src/assets/lab/labTutorial3.png b/src/assets/lab/labTutorial3.png new file mode 100644 index 00000000..11ac08b4 Binary files /dev/null and b/src/assets/lab/labTutorial3.png differ diff --git a/src/assets/lab/no-result.svg b/src/assets/lab/no-result.svg new file mode 100644 index 00000000..7178354a --- /dev/null +++ b/src/assets/lab/no-result.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/linkedin.svg b/src/assets/linkedin.svg new file mode 100644 index 00000000..3ddb34b5 --- /dev/null +++ b/src/assets/linkedin.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/loading.gif b/src/assets/loading.gif new file mode 100644 index 00000000..aaac0761 Binary files /dev/null and b/src/assets/loading.gif differ diff --git a/src/assets/loading.svg b/src/assets/loading.svg deleted file mode 100644 index f49691d1..00000000 --- a/src/assets/loading.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/login/apple.svg b/src/assets/login/apple.svg new file mode 100644 index 00000000..c0d22e95 --- /dev/null +++ b/src/assets/login/apple.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/login/google.svg b/src/assets/login/google.svg new file mode 100644 index 00000000..d97aa4af --- /dev/null +++ b/src/assets/login/google.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/login/kakao.svg b/src/assets/login/kakao.svg new file mode 100644 index 00000000..5505b8ad --- /dev/null +++ b/src/assets/login/kakao.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/login/naver.svg b/src/assets/login/naver.svg new file mode 100644 index 00000000..2261a406 --- /dev/null +++ b/src/assets/login/naver.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/logo/full_logo_white.svg b/src/assets/logo/full_logo_white.svg new file mode 100644 index 00000000..0629ce8a --- /dev/null +++ b/src/assets/logo/full_logo_white.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/logo/logo_white.svg b/src/assets/logo/logo_white.svg new file mode 100644 index 00000000..541daed6 --- /dev/null +++ b/src/assets/logo/logo_white.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/logo/slime.svg b/src/assets/logo/slime.svg new file mode 100644 index 00000000..f3ab11ad --- /dev/null +++ b/src/assets/logo/slime.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/logo_with_title_white.svg b/src/assets/logo_with_title_white.svg new file mode 100644 index 00000000..a3254260 --- /dev/null +++ b/src/assets/logo_with_title_white.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/mask_balloon.png b/src/assets/mask_balloon.png new file mode 100644 index 00000000..7cccad95 Binary files /dev/null and b/src/assets/mask_balloon.png differ diff --git a/src/assets/naverLogin.png b/src/assets/naverLogin.png new file mode 100644 index 00000000..06ab3d8c Binary files /dev/null and b/src/assets/naverLogin.png differ diff --git a/src/assets/noFavorites.png b/src/assets/noFavorites.png new file mode 100644 index 00000000..bbf8e884 Binary files /dev/null and b/src/assets/noFavorites.png differ diff --git a/src/assets/profile.png b/src/assets/profile.png new file mode 100644 index 00000000..23c6d2a3 Binary files /dev/null and b/src/assets/profile.png differ diff --git a/src/assets/right_arrow_thick.svg b/src/assets/right_arrow_thick.svg new file mode 100644 index 00000000..3d6004a2 --- /dev/null +++ b/src/assets/right_arrow_thick.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/shortView.svg b/src/assets/shortView.svg new file mode 100644 index 00000000..6b0b653f --- /dev/null +++ b/src/assets/shortView.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/short_view_mock.png b/src/assets/short_view_mock.png new file mode 100644 index 00000000..816455d7 Binary files /dev/null and b/src/assets/short_view_mock.png differ diff --git a/src/assets/stockScore/bad.png b/src/assets/stockScore/bad.png index 82914a88..a184b557 100644 Binary files a/src/assets/stockScore/bad.png and b/src/assets/stockScore/bad.png differ diff --git a/src/assets/stockScore/excellent.png b/src/assets/stockScore/excellent.png index 7466d6fd..98c31acb 100644 Binary files a/src/assets/stockScore/excellent.png and b/src/assets/stockScore/excellent.png differ diff --git a/src/assets/stockScore/good.png b/src/assets/stockScore/good.png index 75d183d3..7b0e7074 100644 Binary files a/src/assets/stockScore/good.png and b/src/assets/stockScore/good.png differ diff --git a/src/assets/stockScore/normal.png b/src/assets/stockScore/normal.png index e722f190..0e689490 100644 Binary files a/src/assets/stockScore/normal.png and b/src/assets/stockScore/normal.png differ diff --git a/src/assets/stockScore/poor.png b/src/assets/stockScore/poor.png index 93deaaf5..6d565fec 100644 Binary files a/src/assets/stockScore/poor.png and b/src/assets/stockScore/poor.png differ diff --git a/src/assets/swipe_hand.png b/src/assets/swipe_hand.png new file mode 100644 index 00000000..ede53f4f Binary files /dev/null and b/src/assets/swipe_hand.png differ diff --git a/src/assets/thread.svg b/src/assets/thread.svg new file mode 100644 index 00000000..5125db75 --- /dev/null +++ b/src/assets/thread.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/tmp_img.png b/src/assets/tmp_img.png new file mode 100644 index 00000000..2b4fbc03 Binary files /dev/null and b/src/assets/tmp_img.png differ diff --git a/src/assets/withdraw.png b/src/assets/withdraw.png new file mode 100644 index 00000000..9dc4c3ce Binary files /dev/null and b/src/assets/withdraw.png differ diff --git a/src/components/CardList/CardList.Style.ts b/src/components/CardList/CardList.Style.ts new file mode 100644 index 00000000..cfc9f4a9 --- /dev/null +++ b/src/components/CardList/CardList.Style.ts @@ -0,0 +1,44 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const CardListContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '12px', +}); + +const CardListTitle = styled.div({ + display: 'flex', + padding: '0px 20px', + gap: '6px', + alignItems: 'center', + + ['>p']: { + margin: '0px', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + + ['&.title']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_gray2, + flexShrink: '0', + }, + + ['&.update-time']: { + ...theme.font.body14Regular, + color: theme.colors.sub_gray8, + marginLeft: 'auto', + }, + }, + + ['>svg']: { + width: '18px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray6, + flexShrink: '0', + }, +}); + +export { CardListContainer, CardListTitle }; diff --git a/src/components/CardList/CardList.tsx b/src/components/CardList/CardList.tsx index df8cb1b0..607c3dcb 100644 --- a/src/components/CardList/CardList.tsx +++ b/src/components/CardList/CardList.tsx @@ -1,50 +1,34 @@ -import { STOCK_COUNTRY } from '@ts/Types'; -import { useIsMobile } from '@hooks/useIsMobile'; -import { useQueryComponent } from '@hooks/useQueryComponent'; -import StockCard from '@components/CardList/StockCard/StockCard'; -import { StockType } from '@components/Common/Common.Type'; -import SlideView from '@components/SlideView/SlideView'; -import ScoreSlotMachine from '@components/StockSlotMachine/StockSlotMachine'; -import { StockInfo } from '@controllers/api.Type'; -import { useHomeStockFetchQuery } from '@controllers/query'; +import { STOCK_UPDATE_TIME } from '@ts/Constants'; +import { StockCountryKey } from '@ts/StockCountry'; +import useAboutCardList, { CardListType } from '@components/Modal/CenterTutorial/AboutCardList/useAboutCardList'; +import { useHomeStockFetchQuery } from '@controllers/stocks/query'; +import InfoSVG from '@assets/icons/info.svg?react'; +import { CardListContainer, CardListTitle } from './CardList.Style'; +import StockCard from './StockCard/StockCard'; -const CardList = ({ name, country }: { name: StockType; country: STOCK_COUNTRY }) => { - const isHot = name === 'HOT'; - const isMobile = useIsMobile(); - const [curStocks, suspend] = useQueryComponent({ query: useHomeStockFetchQuery(name, country) }); - - return ( - suspend || - (curStocks && ( - - )) - ); +const cardListTitle: Record = { + HOT: '👑 현재 시장 반응 TOP 3', + RISING: '🔥 현재 민심 급상승 중', + DESCENT: '💧 현재 민심 급하락 중', }; -const StockRisingDescend = (curStocks: StockInfo[], country: STOCK_COUNTRY) => { - return curStocks.map((stock: StockInfo) => { - return ; - }); -}; +const CardList = ({ type, country }: { type: CardListType; country: StockCountryKey }) => { + const updateTime = STOCK_UPDATE_TIME[country]; + const { data: stocks } = useHomeStockFetchQuery(type, country); -const StockHot = (curStocks: StockInfo[], country: STOCK_COUNTRY) => { - return curStocks.map((stock: StockInfo) => { - return ( - - ); - }); + const { Modal: AboutCardListModal, openModal: openAboutCardListModal } = useAboutCardList(type); + + return ( + + {AboutCardListModal} + +

{`${cardListTitle[type]}`}

+ openAboutCardListModal({ type })} /> +

어제 {updateTime} 기준

+ + + + ); }; export default CardList; diff --git a/src/components/CardList/StockCard/StockCard.Style.ts b/src/components/CardList/StockCard/StockCard.Style.ts index ab51506f..1eb40cbc 100644 --- a/src/components/CardList/StockCard/StockCard.Style.ts +++ b/src/components/CardList/StockCard/StockCard.Style.ts @@ -1,139 +1,217 @@ import styled from '@emotion/styled'; -import { media, theme, themeColor } from '@styles/themes'; +import { deltaToColor } from '@utils/ScoreConvert'; +import { theme } from '@styles/themes'; -export const StockCardContainer = styled.div({ - background: theme.colors.grayscale100, - borderRadius: '12px', - padding: '24px 32px', +const StockCardContainer = styled.div({ display: 'flex', - flexDirection: 'column-reverse', - gap: '32px', - cursor: 'pointer', - lineHeight: 1, + overflow: 'auto', + scrollSnapType: 'x mandatory', - [':hover']: { - background: theme.colors.grayscale90, + transition: 'background-color 0.1s ease-in-out', + backgroundColor: theme.colors.sub_black, + + ['>div']: { + display: 'flex', + gap: '12px', + padding: '0px 20px 8px', + backgroundColor: theme.colors.sub_black, + }, + + ['::-webkit-scrollbar']: { + height: '6px', + }, + ['::-webkit-scrollbar-track']: { + background: theme.colors.sub_black, }, + ['::-webkit-scrollbar-thumb']: { + background: 'inherit', + borderRadius: '4px', + }, + + ['@media (max-width: 768px)']: { + ['>div']: { + padding: '0px 20px', + }, - [media[0]]: { - padding: '12px', - flexDirection: 'row', - gap: '18px', + msOverflowStyle: 'none', + ['::-webkit-scrollbar']: { + display: 'none', + }, }, }); -export const StockCardTitle = styled.div({ +const StockCardItem = styled.div({ + flexShrink: '0', + display: 'flex', + width: '300px', + scrollSnapAlign: 'center', + background: theme.colors.sub_gray11, + borderRadius: '8px', +}); + +// +const LargeStockCardContainer = styled.div({ + flexShrink: '0', + // display: 'block', display: 'flex', - overflow: 'hidden', - width: '100%', flexDirection: 'column', - color: theme.colors.primary0, - gap: '12px', + width: '100%', + scrollSnapAlign: 'center', + background: theme.colors.sub_gray11, + borderRadius: '8px', - [media[0]]: { - gap: '12px', - justifyContent: 'space-between', + ['>hr']: { + border: `2px solid ${theme.colors.sub_black}`, + margin: '0px', }, }); -export const StockCardTitleContents = styled.div({ +const LargeStockCardHeader = styled.div({ display: 'flex', - flexDirection: 'column', - padding: '12px 0', - gap: '18px', + gap: '8px', + alignItems: 'center', + padding: '12px 12px 8px', + overflow: 'hidden', - [media[0]]: { - padding: '8px 0', - gap: '12px', + ['>p']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_gray3, + margin: '0px', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + + ['>img']: { + width: '24px', + height: '24px', + borderRadius: '50%', + flexShrink: '0', }, }); -export const StockCardTitleName = styled.span({ - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - overflow: 'hidden', - fontSize: '24px', - fontWeight: '700', +const LargeStockCardContent = styled.div({ + display: 'flex', + padding: '16px 12px 12px', + gap: '12px', - [media[0]]: { - fontSize: '15px', + ['>img']: { + width: '102px', + height: '92px', + borderRadius: '4px', }, }); -export const StockCardTitleScore = styled.div( - { +const LargeStockCardContentTextContainer = styled.div({ + display: 'grid', + gridTemplateColumns: 'repeat(2, 1fr)', + width: '100%', + + ['>div']: { display: 'flex', - fontSize: '32px', - fontWeight: '700', + flexDirection: 'column', alignItems: 'center', - gap: '8px', - - ['span']: { - fontSize: '18px', - display: 'flex', - alignItems: 'center', - gap: '4px', - ['svg']: { - height: '0.5em', - width: '0.5em', + justifyContent: 'center', + gap: '4px', + + ['>p']: { + margin: '0px', + + ['&.title']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray7, }, - }, - [media[0]]: { - fontSize: '21px', - ['span']: { - fontSize: '15px', + ['&.content']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_gray5, }, }, }, - ({ diffColor }: { diffColor: themeColor }) => ({ - ['span']: { - color: theme.colors[diffColor], - ['svg']: { - fill: theme.colors[diffColor], - }, - }, - }), -); +}); -export const StockCardKeywords = styled.div({ +const SmallStockCardContainer = styled.div({ + flexShrink: '0', display: 'flex', - gap: '8px', + width: '100%', + scrollSnapAlign: 'center', + background: theme.colors.sub_gray11, + borderRadius: '8px', + padding: '12px', + gap: '16px', + boxSizing: 'border-box', + + ['>img']: { + width: '102px', + height: '92px', + borderRadius: '4px', + }, +}); - fontSize: '15px', +const SmallStockCardContent = styled.div({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + minWidth: '0px', +}); - ['span']: { - background: theme.colors.grayscale90, - padding: '4px 12px', - borderRadius: '32px', - }, - '::after': { - content: '""', - display: 'inline-block', - height: '1em', - padding: '4px 0', - }, +const SmallStockCardContentTitle = styled.div({ + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + width: '100%', - [media[0]]: { - fontSize: '13px', - ['span']: { - padding: '4px 8px', - }, + ['>p']: { + margin: '0px', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + ...theme.font.body18Semibold, + color: theme.colors.primary0, }, }); -export const StockCardImage = styled.div({ - height: '160px', - display: 'flex', - justifyContent: 'end', +const SmallStockCardContentScore = styled.div( + ({ delta, isNew }: { delta: number; isNew: boolean }) => ({ + ['>span']: { + color: isNew ? theme.colors.yellow : (deltaToColor(delta) ?? theme.colors.sub_gray7), + }, + }), + { + ...theme.font.body18Semibold, + color: theme.colors.sub_gray4, - ['img']: { - height: '100%', - borderRadius: '12px', + display: 'flex', + gap: '4px', + alignItems: 'center', + + ['>span']: { + ...theme.font.body14Semibold, + }, }, +); + +const SmallStockCardContentKeywords = styled.div({ + display: 'flex', + gap: '10px', - [media[0]]: { - justifyContent: 'start', - height: '100px', + ['>p']: { + margin: '0px', + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, }, }); + +export { + StockCardContainer, + StockCardItem, + LargeStockCardContainer, + LargeStockCardHeader, + LargeStockCardContent, + LargeStockCardContentTextContainer, + SmallStockCardContainer, + SmallStockCardContent, + SmallStockCardContentTitle, + SmallStockCardContentScore, + SmallStockCardContentKeywords, +}; diff --git a/src/components/CardList/StockCard/StockCard.tsx b/src/components/CardList/StockCard/StockCard.tsx index 1e846741..70eab28a 100644 --- a/src/components/CardList/StockCard/StockCard.tsx +++ b/src/components/CardList/StockCard/StockCard.tsx @@ -1,55 +1,121 @@ -import { useNavigate } from 'react-router-dom'; -import { STOCK_COUNTRY } from '@ts/Types'; -import { deltaColor } from '@utils/Delta'; -import { scoreToImage } from '@utils/ScoreConvert'; -import { webPath } from '@router/index'; -import { StockInfo } from '@controllers/api.Type'; -import DownSVG from '@assets/icons/down.svg?react'; -import UpSVG from '@assets/icons/up.svg?react'; +import { StockCountryKey } from '@ts/StockCountry'; +import { diffToValue, scoreToImage, scoreToText } from '@utils/ScoreConvert'; +import useRouter from '@router/useRouter'; +import StockImage from '@components/Common/StockImage'; +import { StockInfo } from '@controllers/stocks/types'; import { + LargeStockCardContainer, + LargeStockCardContent, + LargeStockCardContentTextContainer, + LargeStockCardHeader, + SmallStockCardContainer, + SmallStockCardContent, + SmallStockCardContentKeywords, + SmallStockCardContentScore, + SmallStockCardContentTitle, StockCardContainer, - StockCardImage, - StockCardKeywords, - StockCardTitle, - StockCardTitleContents, - StockCardTitleName, - StockCardTitleScore, + StockCardItem, } from './StockCard.Style'; -const signedNumber = (value: number) => { - const sign = value > 0 ? '+' : value < 0 ? '-' : ''; - return sign + Math.abs(value); -}; +export const LargeStockCard = ({ + stock: { stockId, symbolName, score }, + country, +}: { + stock: StockInfo; + country: StockCountryKey; +}) => { + const { navToStock } = useRouter(); + + const handleClick = () => { + navToStock(symbolName, country); + }; -const StockCard = ({ stockInfo, country }: { stockInfo: StockInfo; country: STOCK_COUNTRY }) => { - const navigate = useNavigate(); - const { symbolName, score, diff, keywords } = stockInfo; - const deltaSVG = !diff ? ' -' : diff > 0 ? : ; const scoreImage = scoreToImage(score); + const scoreText = scoreToText(score); + + return ( + + + +

{symbolName}

+
+
+ + + +
+

민심 키워드

+

{scoreText}

+
+
+

민심 점수

+

{score}점

+
+
+
+
+ ); +}; + +export const SmallStockCard = ({ + stock: { stockId, symbolName, score, diff, keywords }, + country, +}: { + stock: StockInfo; + country: StockCountryKey; +}) => { + const { navToStock } = useRouter(); const handleClick = () => { - navigate(webPath.search(), { state: { symbolName: symbolName, country: country } }); + navToStock(symbolName, country); }; + const scoreImage = scoreToImage(score); + + return ( + + + + +

{symbolName}

+ + {score}점{score != diff ? `${diffToValue(diff)}점` : 'NEW!'} + +
+ + {keywords?.map((e) =>

#{e}

)} +
+
+
+ ); +}; + +const StockCard = ({ + type, + stocks, + size, + country, +}: { + type: string; + stocks: StockInfo[]; + size: 'large' | 'small'; + country: StockCountryKey; +}) => { + if (!stocks) return null; + return ( - - - - - - - {symbolName} - - {score}점 - - {signedNumber(diff)}점{deltaSVG} - - - - - {keywords?.map((e, i) => {e})} - - + +
+ {stocks.map((stock: StockInfo) => ( + + {size === 'large' ? ( + + ) : ( + + )} + + ))} +
); }; diff --git a/src/components/Common/Button.ts b/src/components/Common/Button.ts new file mode 100644 index 00000000..6e7ed46d --- /dev/null +++ b/src/components/Common/Button.ts @@ -0,0 +1,17 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const Button = styled.button({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '8px', + background: theme.colors.sub_blue6, + border: 'none', + borderRadius: '8px', + padding: '10px 0px', + ...theme.font.body18Semibold, + color: theme.colors.sub_white, +}); + +export default Button; diff --git a/src/components/Common/Common.Style.ts b/src/components/Common/Common.Style.ts new file mode 100644 index 00000000..d011e57c --- /dev/null +++ b/src/components/Common/Common.Style.ts @@ -0,0 +1,85 @@ +import styled from '@emotion/styled'; +import { theme } from '../../styles/themes'; +import { AbsoluteDivProps, ButtonDivProps, FlexDivProps, ImgDivProps } from './Common.Props'; + +const FlexDiv = styled.div( + ({ + flexDirection = 'row', + alignItems = 'normal', + justifyContent = 'normal', + padding = '0', + width = 'auto', + gap = '0', + }: FlexDivProps) => ({ + display: 'flex', + flexDirection: flexDirection, + alignItems: alignItems, + justifyContent: justifyContent, + padding: padding, + width: width, + gap: gap, + }), +); + +const ButtonDiv = styled.div( + ({ gap = '0', padding = '0', background, width = 'auto', height = 'auto', radius = 'auto' }: ButtonDivProps) => ({ + display: 'flex', + padding: padding, + background: theme.colors[background ?? 'transparent'], + cursor: 'pointer', + gap: gap, + width: width, + height: height, + borderRadius: radius, + }), +); + +const RelativeDiv = styled.div({ + position: 'relative', +}); + +const AbsoluteDiv = styled.div( + { position: 'absolute' }, + ({ + width = 'auto', + height = 'auto', + top = 'auto', + bottom = 'auto', + left = 'auto', + right = 'auto', + }: AbsoluteDivProps) => ({ + width: width, + height: height, + top: top, + bottom: bottom, + left: left, + right: right, + }), +); + +const ImgDiv = styled.img(({ width = 'auto', height = 'auto', objectFit = 'contain' }: ImgDivProps) => ({ + width: width, + height: height, + objectFit: objectFit, +})); + +const Container = styled.div({ + display: 'flex', + flexDirection: 'column', + boxSizing: 'border-box', + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center', + overflow: 'hidden', + + ['>img']: { + objectFit: 'contain', + objectPosition: 'center', + + width: '100%', + height: '100%', + }, +}); + +export { FlexDiv, ButtonDiv, RelativeDiv, AbsoluteDiv, ImgDiv, Container }; diff --git a/src/components/Common/Common.Type.ts b/src/components/Common/Common.Type.ts index 9884648a..457ced9f 100644 --- a/src/components/Common/Common.Type.ts +++ b/src/components/Common/Common.Type.ts @@ -4,7 +4,14 @@ export type SelfPosition = 'center' | 'end' | 'flex-end' | 'flex-start' | 'self- export type AlignItems = Globals | SelfPosition | 'baseline' | 'normal' | 'stretch' | (string & {}); export type ContentDistribution = 'space-around' | 'space-between' | 'space-evenly' | 'stretch'; export type ContentPosition = 'center' | 'end' | 'flex-end' | 'flex-start' | 'start'; -export type JustifyContent = Globals | ContentDistribution | ContentPosition | 'left' | 'normal' | 'right' | (string & {}); +export type JustifyContent = + | Globals + | ContentDistribution + | ContentPosition + | 'left' + | 'normal' + | 'right' + | (string & {}); export type FlexDirection = Globals | 'column' | 'column-reverse' | 'row' | 'row-reverse'; export type Width = | Globals @@ -33,5 +40,3 @@ export type Height = | 'min-content' | string; export type Padding = Globals | TLength | string; - -export type StockType = 'HOT' | 'RISING' | 'DESCENT'; diff --git a/src/components/Common/Common.tsx b/src/components/Common/Common.tsx index 4fdd9ee2..81e314b3 100644 --- a/src/components/Common/Common.tsx +++ b/src/components/Common/Common.tsx @@ -1,69 +1,9 @@ import styled from '@emotion/styled'; import { media, theme, themeColor, themeFontSizeBody, themeTextType } from '../../styles/themes'; -import { AbsoluteDivProps, ButtonDivProps, FlexDivProps, ImgDivProps } from './Common.Props'; -const FlexDiv = styled.div( - ({ - flexDirection = 'row', - alignItems = 'normal', - justifyContent = 'normal', - padding = '0', - width = 'auto', - gap = '0', - }: FlexDivProps) => ({ - display: 'flex', - flexDirection: flexDirection, - alignItems: alignItems, - justifyContent: justifyContent, - padding: padding, - width: width, - gap: gap, - }), -); +export { FlexDiv, ButtonDiv, RelativeDiv, AbsoluteDiv, ImgDiv, Container } from './Common.Style'; -const ButtonDiv = styled.div( - ({ gap = '0', padding = '0', background, width = 'auto', height = 'auto', radius = 'auto' }: ButtonDivProps) => ({ - display: 'flex', - padding: padding, - background: theme.colors[background ?? 'transparent'], - cursor: 'pointer', - gap: gap, - width: width, - height: height, - borderRadius: radius, - }), -); - -const RelativeDiv = styled.div({ - position: 'relative', -}); - -const AbsoluteDiv = styled.div( - { position: 'absolute' }, - ({ - width = 'auto', - height = 'auto', - top = 'auto', - bottom = 'auto', - left = 'auto', - right = 'auto', - }: AbsoluteDivProps) => ({ - width: width, - height: height, - top: top, - bottom: bottom, - left: left, - right: right, - }), -); - -const ImgDiv = styled.img(({ width = 'auto', height = 'auto', objectFit = 'contain' }: ImgDivProps) => ({ - width: width, - height: height, - objectFit: objectFit, -})); - -const StyledSVG = ({ +export const StyledSVG = ({ svg, fill, stroke, @@ -94,20 +34,3 @@ const StyledSVG = ({ }); return ; }; - -const Container = styled.div({ - display: 'flex', - boxSizing: 'border-box', - width: '100%', - height: '100%', - justifyContent: 'center', - alignItems: 'center', - borderRadius: '16px', - padding: '15%', - - ['svg']: { - width: '100%', - }, -}); - -export { FlexDiv, ButtonDiv, RelativeDiv, AbsoluteDiv, ImgDiv, StyledSVG, Container }; diff --git a/src/components/Common/ContentsItem.Style.ts b/src/components/Common/ContentsItem.Style.ts deleted file mode 100644 index 2b2ea7db..00000000 --- a/src/components/Common/ContentsItem.Style.ts +++ /dev/null @@ -1,61 +0,0 @@ -import styled from '@emotion/styled'; -import { media, theme, themeColor } from '@styles/themes'; - -const ContentsItemContainer = styled.div({ - display: 'flex', - flexDirection: 'column', - gap: '24px', - - [media[0]]: { - gap: '18px', - }, -}); - -const ContentsItemTitle = styled.div(({ color }: { color?: themeColor }) => ({ - display: 'flex', - gap: '8px', - alignItems: 'center', - - color: theme.colors.grayscale10, - fontWeight: '700', - fontSize: '32px', - - ['.btn_info']: { - height: '0.8em', - marginLeft: '4px', - - cursor: 'pointer', - }, - - ['svg']: { - width: 'auto', - height: '0.9em', - - fill: color ? theme.colors[color] : '', - }, - - [media[0]]: { - gap: '6px', - padding: '0 20px', - - fontSize: '24px', - - ['.btn_info']: { - marginLeft: '0px', - }, - }, -})); - -const ContentsItemContent = styled.div({ - display: 'flex', - flexDirection: 'column', - gap: '18px', - margin: '0 32px', - - [media[0]]: { - margin: '0 0px', - padding: '0 20px', - }, -}); - -export { ContentsItemContainer, ContentsItemTitle, ContentsItemContent }; diff --git a/src/components/Common/GuideBanner/GuideBanner.tsx b/src/components/Common/GuideBanner/GuideBanner.tsx new file mode 100644 index 00000000..c23dabda --- /dev/null +++ b/src/components/Common/GuideBanner/GuideBanner.tsx @@ -0,0 +1,65 @@ +import styled from '@emotion/styled'; +import useRouter from '@router/useRouter'; +import { theme } from '@styles/themes'; +import DictSVG from '@assets/footer/footer_dict.svg?react'; + +const GuideBannerContainer = styled.div({ + display: 'flex', + alignItems: 'center', + padding: '12px 14px', + background: theme.colors.sub_gray11, + borderRadius: '6px', + gap: '16px', + cursor: 'pointer', + + ['>svg']: { + width: '24px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray1, + padding: '12px', + background: theme.colors.sub_gray10, + borderRadius: '999px', + flexShrink: '0', + }, +}); + +const GuideBannerTextGroup = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '2px', + + ['>p']: { + margin: '0', + + ['&.title']: { + ...theme.font.body16Semibold, + color: theme.colors.sub_gray3, + }, + + ['&.description']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + whiteSpace: 'nowrap', + }, + }, +}); + +const GuideBanner = () => { + const { navToAbout } = useRouter(); + + return ( + + + +

도대체 인간지표가 뭐지?

+

+ 내가 사면 떨어지고, + 내가 팔면 오르는 마법?? +

+
+
+ ); +}; + +export default GuideBanner; diff --git a/src/components/Common/Header.Style.ts b/src/components/Common/Header.Style.ts new file mode 100644 index 00000000..bb90e787 --- /dev/null +++ b/src/components/Common/Header.Style.ts @@ -0,0 +1,38 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const HeaderContainer = styled.div({ + paddingBottom: '8px', + borderBottom: `4px solid ${theme.colors.sub_gray11}`, +}); + +const HeaderContents = styled.div({ + position: 'relative', + display: 'flex', + alignItems: 'center', + padding: '8px 20px', + gap: '12px', + + ['>p']: { + position: 'absolute', + left: '50%', + transform: 'translateX(-50%)', + ...theme.font.body18Semibold, + color: theme.colors.sub_white, + margin: '0', + }, + + ['>svg']: { + width: '32px', + height: 'auto', + aspectRatio: '1 / 1', + cursor: 'pointer', + fill: theme.colors.sub_gray5, + }, + + ['>span']: { + flexGrow: 1, + }, +}); + +export { HeaderContainer, HeaderContents }; diff --git a/src/components/Common/Header.tsx b/src/components/Common/Header.tsx new file mode 100644 index 00000000..dbaafc09 --- /dev/null +++ b/src/components/Common/Header.tsx @@ -0,0 +1,37 @@ +import { useNavigate } from 'react-router-dom'; +import ArrowLeftSVG from '@assets/arrowLeft.svg?react'; +import CloseSVG from '@assets/close.svg?react'; +import { HeaderContainer, HeaderContents } from './Header.Style'; + +const Header = ({ + title, + onBefore, + beforeIconType = 'back', +}: { + title: string; + onBefore?: () => void; + beforeIconType?: 'back' | 'close'; +}) => { + const navigate = useNavigate(); + + const handleBefore = () => { + if (onBefore) { + onBefore(); + } else { + navigate(-1); + } + }; + + return ( + + +

{title}

+ {beforeIconType === 'back' && } + + {beforeIconType === 'close' && } +
+
+ ); +}; + +export default Header; diff --git a/src/components/Common/LoadingComponent.tsx b/src/components/Common/LoadingComponent.tsx index 337615fd..ab10357a 100644 --- a/src/components/Common/LoadingComponent.tsx +++ b/src/components/Common/LoadingComponent.tsx @@ -1,10 +1,10 @@ -import LoadingSVG from '@assets/loading.svg?react'; +import LoadingGIF from '@assets/loading.gif'; import { Container } from './Common'; const LoadingComponent = () => { return ( - + ); }; diff --git a/src/components/Common/Marquee/Marquee.Style.ts b/src/components/Common/Marquee/Marquee.Style.ts new file mode 100644 index 00000000..24a353e8 --- /dev/null +++ b/src/components/Common/Marquee/Marquee.Style.ts @@ -0,0 +1,21 @@ +import styled from '@emotion/styled'; +import { motion } from 'framer-motion'; + +const MarqueeContainer = styled.div({ + position: 'relative', + overflow: 'hidden', + whiteSpace: 'nowrap', + + ['>div.hidden']: { + visibility: 'hidden', + }, +}); + +const MarqueeContent = styled(motion.div)({ + willChange: 'transform', + position: 'absolute', + top: 0, + left: 0, +}); + +export { MarqueeContainer, MarqueeContent }; diff --git a/src/components/Common/Marquee/Marquee.tsx b/src/components/Common/Marquee/Marquee.tsx new file mode 100644 index 00000000..977cc864 --- /dev/null +++ b/src/components/Common/Marquee/Marquee.tsx @@ -0,0 +1,143 @@ +import { useAnimationControls } from 'framer-motion'; +import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { MarqueeContainer, MarqueeContent } from './Marquee.Style'; + +const PX_PER_SEC = 90; + +const Marquee = ({ + children, + startDelaySec = 1, + endDelaySec = 1, +}: { + children: React.ReactNode; + startDelaySec?: number; + endDelaySec?: number; +}) => { + const wrapperRef = useRef(null); + const roRef = useRef(null); + const timerRef = useRef(null); + + const controls = useAnimationControls(); + + const [metrics, setMetrics] = useState({ + overflow: false, + offsetWidth: 0, + scrollWidth: 0, + }); + + const travelPx = useMemo(() => { + return metrics.overflow ? Math.max(0, metrics.scrollWidth - metrics.offsetWidth) : 0; + }, [metrics]); + + const moveSec = useMemo(() => { + if (travelPx <= 0) return 0; + return Math.max(0.5, travelPx / PX_PER_SEC); + }, [travelPx]); + + const clearTimer = () => { + if (timerRef.current != null) { + window.clearTimeout(timerRef.current); + timerRef.current = null; + } + }; + + const sleep = (ms: number) => + new Promise((resolve) => { + clearTimer(); + timerRef.current = window.setTimeout(() => { + timerRef.current = null; + resolve(); + }, ms); + }); + + useLayoutEffect(() => { + const el = wrapperRef.current; + if (!el) return; + + const measure = () => { + const { offsetWidth, scrollWidth } = el; + setMetrics({ + overflow: scrollWidth > offsetWidth, + offsetWidth, + scrollWidth, + }); + }; + + measure(); + + roRef.current?.disconnect(); + roRef.current = new ResizeObserver(measure); + roRef.current.observe(el); + + return () => { + roRef.current?.disconnect(); + roRef.current = null; + }; + }, []); + + useLayoutEffect(() => { + const el = wrapperRef.current; + if (!el) return; + const { offsetWidth, scrollWidth } = el; + setMetrics({ + overflow: scrollWidth > offsetWidth, + offsetWidth, + scrollWidth, + }); + }, [children]); + + useEffect(() => { + if (travelPx <= 0) { + clearTimer(); + controls.stop(); + controls.set({ x: 0 }); + } + }, [controls, travelPx]); + + useEffect(() => { + if (travelPx <= 0 || moveSec <= 0) return; + + let cancelled = false; + + const run = async () => { + while (!cancelled) { + controls.set({ x: 0 }); + + if (startDelaySec > 0) { + await sleep(startDelaySec * 1000); + if (cancelled) return; + } + + await controls.start({ + x: -travelPx, + transition: { duration: moveSec, ease: 'linear' }, + }); + if (cancelled) return; + + if (endDelaySec > 0) { + await sleep(endDelaySec * 1000); + if (cancelled) return; + } + + controls.set({ x: 0 }); + } + }; + + run(); + + return () => { + cancelled = true; + clearTimer(); + controls.stop(); + }; + }, [controls, travelPx, moveSec, startDelaySec, endDelaySec]); + + return ( + +
{children}
+ {children} +
+ ); +}; + +export default Marquee; diff --git a/src/components/Common/ScrollTopButton/ScrollTopButton.Style.ts b/src/components/Common/ScrollTopButton/ScrollTopButton.Style.ts new file mode 100644 index 00000000..236998ad --- /dev/null +++ b/src/components/Common/ScrollTopButton/ScrollTopButton.Style.ts @@ -0,0 +1,31 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const ScrollTopButtonContainer = styled.div( + ({ isHidden }: { isHidden: boolean }) => ({ + display: isHidden ? 'none' : 'flex', + }), + { + position: 'fixed', + bottom: '96px', + right: '0', + width: '58px', + height: '58px', + margin: '20px', + borderRadius: '50%', + alignItems: 'center', + justifyContent: 'center', + background: 'rgba(52, 58, 64, 0.5)', + boxShadow: '5px 5px 10px rgba(0, 0, 0, 0.7)', + backdropFilter: 'blur(10px)', + + ['>svg']: { + width: '32px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_white, + }, + }, +); + +export { ScrollTopButtonContainer }; diff --git a/src/components/Common/ScrollTopButton/ScrollTopButton.tsx b/src/components/Common/ScrollTopButton/ScrollTopButton.tsx new file mode 100644 index 00000000..d867b742 --- /dev/null +++ b/src/components/Common/ScrollTopButton/ScrollTopButton.tsx @@ -0,0 +1,30 @@ +import { useEffect, useState } from 'react'; +import ArrowUpSVG from '@assets/icons/arrowUp.svg?react'; +import { ScrollTopButtonContainer } from './ScrollTopButton.Style'; + +const ScrollTopButton = () => { + const [isScrolled, setIsScrolled] = useState(false); + + const handleClickScrollTop = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth', // 부드럽게 스크롤 + }); + }; + + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 0); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( + + + + ); +}; + +export default ScrollTopButton; diff --git a/src/components/Common/StockImage.tsx b/src/components/Common/StockImage.tsx new file mode 100644 index 00000000..432f6906 --- /dev/null +++ b/src/components/Common/StockImage.tsx @@ -0,0 +1,47 @@ +import { useEffect, useState } from 'react'; +import { DEFAULT_STOCK_IMAGE, resolveStockImageUrl } from '@utils/stockImage'; + +interface StockImageProps { + stockId: number; + imageUrl?: string | null; + alt?: string; + className?: string; + style?: React.CSSProperties; + onError?: () => void; +} + +/** + * 주식 이미지 컴포넌트 + * 이미지 로딩 실패 시 자동으로 기본 이미지로 fallback + */ +const StockImage = ({ + stockId, + imageUrl, + alt = 'Stock image', + className, + style, + onError: customOnError, +}: StockImageProps) => { + const [imageError, setImageError] = useState(false); + const [imageSrc, setImageSrc] = useState(resolveStockImageUrl(stockId, imageUrl)); + + // stockId나 imageUrl이 변경되면 이미지 URL 재설정 + useEffect(() => { + const newUrl = resolveStockImageUrl(stockId, imageUrl); + setImageSrc(newUrl); + setImageError(false); + }, [stockId, imageUrl]); + + // 이미지 로딩 실패 시 기본 이미지로 변경 + const handleImageError = () => { + if (!imageError) { + setImageError(true); + setImageSrc(DEFAULT_STOCK_IMAGE); + customOnError?.(); + } + }; + + return {alt}; +}; + +export default StockImage; diff --git a/src/components/Event/Disquiet.Style.ts b/src/components/Event/Disquiet.Style.ts index 43dcfd88..4f496d7a 100644 --- a/src/components/Event/Disquiet.Style.ts +++ b/src/components/Event/Disquiet.Style.ts @@ -23,7 +23,7 @@ const elevator = keyframes({ }, }); -export const DisquietViewStyled = styled.div({ +const DisquietViewStyled = styled.div({ position: 'absolute', top: 0, right: 300, @@ -39,7 +39,7 @@ export const DisquietViewStyled = styled.div({ animation: shiny + ' 12s ease-in-out 1s infinite', }); -export const DisquietViewContainer = styled.div({ +const DisquietViewContainer = styled.div({ position: 'absolute', top: '0', right: '0', @@ -89,7 +89,7 @@ export const DisquietViewContainer = styled.div({ }, }); -export const DisquietViewTitleContainer = styled.div({ +const DisquietViewTitleContainer = styled.div({ display: 'flex', flexDirection: 'column', gap: '12px', @@ -114,3 +114,5 @@ export const DisquietViewTitleContainer = styled.div({ }, }, }); + +export { DisquietViewStyled, DisquietViewContainer, DisquietViewTitleContainer }; diff --git a/src/components/Home/IndexScore/IndexScore.style.ts b/src/components/Home/IndexScore/IndexScore.style.ts deleted file mode 100644 index 0741e146..00000000 --- a/src/components/Home/IndexScore/IndexScore.style.ts +++ /dev/null @@ -1,83 +0,0 @@ -import styled from '@emotion/styled'; -import { deltaScoreToColor } from '@utils/ScoreConvert'; -import { media, theme } from '@styles/themes'; - -const IndicesContainer = styled.div({ - display: 'flex', - flexDirection: 'row', - gap: '8px', - alignItems: 'center', - justifyContent: 'center', - - whiteSpace: 'nowrap', -}); - -const IndexItem = styled.div({ - display: 'flex', - flex: 1, - gap: '4px', - justifyContent: 'space-between', - boxSizing: 'border-box', - padding: '18px 24px', - - color: theme.colors.primary0, - fontFamily: 'Pretendard', - fontStyle: 'normal', - lineHeight: '1.5', - - background: theme.colors.grayscale100, - borderRadius: '8px', - - [media[0]]: { - flexDirection: 'column', - gap: '8px', - alignItems: 'flex-start', - justifyContent: 'center', - padding: '12px', - }, -}); - -const IndexInfoContainer = styled.div({ - display: 'flex', - gap: '4px', - alignItems: 'center', - - fontWeight: '500', - fontSize: '18px', - - [media[0]]: { - fontWeight: '700', - fontSize: '11px', - }, - - ['svg']: { - width: 'auto', - height: '1.25em', - }, -}); - -const IndexDeltaScore = styled.div(({ delta }: { delta: number }) => ({ - display: 'flex', - gap: '4px', - alignItems: 'center', - - color: deltaScoreToColor(delta), - fontWeight: '700', - fontSize: '32px', - lineHeight: '1', - - [media[0]]: { - margin: '0 4px', - - fontSize: '24px', - }, - - ['svg']: { - width: 'auto', - height: '0.5em', - - fill: deltaScoreToColor(delta), - }, -})); - -export { IndicesContainer, IndexInfoContainer, IndexItem, IndexDeltaScore }; diff --git a/src/components/Home/IndexScore/IndexScore.tsx b/src/components/Home/IndexScore/IndexScore.tsx deleted file mode 100644 index f4fbee11..00000000 --- a/src/components/Home/IndexScore/IndexScore.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useState } from 'react'; -import { useQueryComponent } from '@hooks/useQueryComponent'; -import FearPopUp from '@components/PopUp/FearPopUp/FearPopUp'; -import { useIndexScoreQuery } from '@controllers/query'; -import DownSVG from '@assets/icons/down.svg?react'; -import UpSVG from '@assets/icons/up.svg?react'; -import InfoSVG from '@assets/info.svg?react'; -import { IndexDeltaScore, IndexInfoContainer, IndexItem, IndicesContainer } from './IndexScore.style'; - -const stockIndices = [ - ['공포탐욕지수 ', '코스피', '코스닥'], - ['공포탐욕지수 ', 'S&P 500', '나스닥'], -]; - -const IndexScore = ({ tabIndex }: { tabIndex: number }) => { - const [indexScores, suspend] = useQueryComponent({ query: useIndexScoreQuery() }); - - const [isPopupOpen, setPopupOpen] = useState(false); - const togglePopup = () => setPopupOpen((prev) => !prev); - - const entries = Object.entries(indexScores ?? []); - - const transformed = entries.reduce<{ score: number; delta: number }[]>((acc, _, i) => { - if (i % 2 === 0) { - acc.push({ - score: entries[i][1] as number, - delta: entries[i + 1]?.[1] as number, - }); - } - return acc; - }, []); - - const splitIndex = transformed.length / 2; - const result = tabIndex === 0 ? transformed.slice(0, splitIndex) : transformed.slice(splitIndex); - - if (suspend) return null; - - return ( - - {result.map(({ score, delta }, idx) => ( - - - {stockIndices[tabIndex][idx]} - {idx === 0 && } - - - {Math.abs(score)}점 {delta === 0 ? '-' : delta > 0 ? : } - - - ))} - {isPopupOpen && } - - ); -}; - -export default IndexScore; diff --git a/src/components/Home/Keywords/Keywords.style.tsx b/src/components/Home/Keywords/Keywords.style.tsx deleted file mode 100644 index cc07c48e..00000000 --- a/src/components/Home/Keywords/Keywords.style.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import styled from '@emotion/styled'; -import { media, theme } from '@styles/themes'; - -const KeywordsContainer = styled.div({ - display: 'flex', - flexDirection: 'column', - gap: '20px', -}); - -const TitleWrapper = styled.div({ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - - fontFamily: 'Pretendard', - whiteSpace: 'nowrap', - - ['> span']: { - color: theme.colors.grayscale60, - fontWeight: '500', - fontSize: '15px', - }, - - [media[0]]: { - padding: '0 20px', - - ['> span']: { - fontSize: '11px', - }, - }, -}); - -const Title = styled.div({ - display: 'flex', - alignItems: 'center', - - color: theme.colors.grayscale10, - fontWeight: 700, - fontSize: '32px', - fontStyle: 'normal', - lineHeight: '150%', - - ['svg']: { - width: '24px', - height: '24px', - marginLeft: '8px', - }, - - [media[0]]: { - fontSize: '16px', - - ['svg']: { - width: '20px', - }, - }, -}); - -const KeywordList = styled.div({ - overflow: 'auto', - - whiteSpace: 'nowrap', - - msOverflowStyle: 'none', - - ['::-webkit-scrollbar']: { - display: 'none', - }, - - [media[0]]: { - padding: '0 20px', - }, -}); - -const KeywordItemConainer = styled.div({ - display: 'flex', - gap: '12px', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - - [media[0]]: { - gap: '8px', - justifyContent: 'start', - }, -}); - -const KeywordItem = styled.div({ - padding: '8px 24px', - - color: theme.colors.primary0, - fontWeight: 700, - fontSize: '19px', - fontFamily: 'Pretendard', - textAlign: 'right', - - backgroundColor: theme.colors.grayscale100, - borderRadius: '30px', - - [media[0]]: { - padding: '8px 12px', - - fontSize: '13px', - }, -}); - -export { KeywordsContainer, TitleWrapper, Title, KeywordList, KeywordItemConainer, KeywordItem }; diff --git a/src/components/Home/Keywords/Keywords.tsx b/src/components/Home/Keywords/Keywords.tsx deleted file mode 100644 index c96871e2..00000000 --- a/src/components/Home/Keywords/Keywords.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useState } from 'react'; -import { STOCK_UPDATE_TIME } from '@ts/Constants'; -import { useQueryComponent } from '@hooks/useQueryComponent'; -import KeywordPopUp from '@components/PopUp/KeywordPopUp/KeywordPopUp'; -import { useKeywordsQuery } from '@controllers/query'; -import InfoSVG from '@assets/info.svg?react'; -import { - KeywordItem, - KeywordItemConainer, - KeywordList, - KeywordsContainer, - Title, - TitleWrapper, -} from './Keywords.style'; - -const Keywords = ({ country }: { country: string }) => { - const [keywords, suspend] = useQueryComponent({ query: useKeywordsQuery(country) }); - - const [isPopupOpen, setPopupOpen] = useState(false); - const togglePopup = () => setPopupOpen((prev) => !prev); - - const updateTime = STOCK_UPDATE_TIME[country]; - return ( - - - - 오늘 가장 많이 언급된 키워드 - <InfoSVG onClick={togglePopup} /> - - 매일 {updateTime}시 업데이트됩니다. - - - - {suspend || - (keywords && - keywords.map((keyword: string, index: number) => ( - {}}> - {keyword.toLocaleUpperCase()} - - )))} - - - {isPopupOpen && } - - ); -}; - -export default Keywords; diff --git a/src/components/Home/StockTable/StockTable.style.ts b/src/components/Home/StockTable/StockTable.style.ts deleted file mode 100644 index 6d6aef48..00000000 --- a/src/components/Home/StockTable/StockTable.style.ts +++ /dev/null @@ -1,195 +0,0 @@ -import styled from '@emotion/styled'; -import { deltaScoreToColor } from '@utils/ScoreConvert'; -import { media, theme } from '@styles/themes'; - -const StockTableContainer = styled.div({ - boxSizing: 'border-box', - width: '100%', - - [media[0]]: { - padding: '0 20px', - }, -}); - -const StockTableTitle = styled.div({ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - margin: 0, - padding: '10px 0', - - color: theme.colors.grayscale10, - fontWeight: '700', - fontSize: '32px', - fontFamily: 'Pretendard', - lineHeight: '1.5', - - ['div']: { - display: 'flex', - alignItems: 'center', - }, - - ['span']: { - color: theme.colors.grayscale60, - fontWeight: '500', - fontSize: '15px', - }, - - ['svg']: { - width: 'auto', - height: '28px', - marginLeft: '8px', - }, - - [media[0]]: { - padding: '5px 0', - - fontSize: '24px', - - ['span']: { - fontSize: '11px', - }, - - ['svg']: { - height: '0.9em', - }, - }, -}); - -const StyledTabMenu = styled.ul({ - display: 'flex', - alignItems: 'center', - margin: '0', - padding: 0, - - backgroundColor: theme.colors.primary100, - - '.focused': { - fontWeight: '700', - - backgroundColor: theme.colors.grayscale100, - }, - - '.submenu': { - display: 'flex', - justifyContent: 'center', - boxSizing: 'border-box', - padding: '8px 12px', - - color: theme.colors.primary0, - fontWeight: '500', - - backgroundColor: theme.colors.primary100, - borderRadius: '8px', - cursor: 'pointer', - }, - - [media[0]]: { - alignItems: 'center', - justifyContent: 'space-around', - }, -}); - -const HeaderItem = styled.div({ - flex: 1, - - textAlign: 'center', -}); - -const TableHeaderContainer = styled.div({ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: '10px 0', - - color: theme.colors.grayscale60, - fontWeight: 500, - fontSize: '16px', - fontFamily: 'Pretendard', - fontStyle: 'normal', - lineHeight: '1.5', - - [media[0]]: { - padding: '5px 0', - - fontSize: '12px', - }, -}); - -const TableRow = styled.div({ - display: 'grid', - gridTemplateColumns: '33% 33% 33%', - alignItems: 'center', - justifyContent: 'center', - padding: '12px 0', - - color: theme.colors.primary0, - - borderBottom: `1px solid ${theme.colors.grayscale90}`, - cursor: 'pointer', - - ':last-child': { - borderBottom: 'none', - }, -}); - -const StockInfo = styled.div({ - display: 'flex', - gap: '8px', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - - textAlign: 'center', -}); - -const StockLogo = styled.div({ - width: '1.5em', - height: '1.5em', - - borderRadius: '64px', -}); - -const StockName = styled.div({ - overflow: 'hidden', - - textOverflow: 'ellipsis', - wordBreak: 'keep-all', -}); - -const StockData = styled.div({ - display: 'flex', - flex: 1, - flexDirection: 'column', - gap: '0', - justifyContent: 'center', - - color: theme.colors.primary0, - fontWeight: 500, - fontSize: '17px', - fontFamily: 'Pretendard', - fontStyle: 'normal', - lineHeight: '1.5', - textAlign: 'center', -}); - -const DeltaScore = styled.span(({ delta }: { delta: number }) => ({ - gap: '8px', - - color: deltaScoreToColor(delta), - fontSize: '13px', -})); - -export { - StockTableContainer, - StockInfo, - StockName, - StockTableTitle, - StyledTabMenu, - TableHeaderContainer, - HeaderItem, - TableRow, - StockLogo, - StockData, - DeltaScore, -}; diff --git a/src/components/Home/StockTable/StockTable.tsx b/src/components/Home/StockTable/StockTable.tsx deleted file mode 100644 index bef6160a..00000000 --- a/src/components/Home/StockTable/StockTable.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { STOCK_UPDATE_TIME } from '@ts/Constants'; -import { useQueryComponent } from '@hooks/useQueryComponent'; -import { webPath } from '@router/index'; -import { StockTableInfo } from '@controllers/api.Type'; -import { useStockTableInfoQuery } from '@controllers/query'; -import HumanIndexSVG from '@assets/HumanIndex.svg?react'; -import { - DeltaScore, - HeaderItem, - StockData, - StockInfo, // StockLogo, - StockName, - StockTableContainer, - StockTableTitle, - StyledTabMenu, - TableHeaderContainer, - TableRow, -} from './StockTable.style'; - -const tabMenu = ['시가총액', '거래량', '급상승', '급하락']; -const categories = ['MARKET', 'VOLUME', 'RISING', 'DESCENT']; - -const StockTable = ({ country }: { country: string }) => { - const navigate = useNavigate(); - const [tabIndex, setTabIndex] = useState(0); - - const updateTime = STOCK_UPDATE_TIME[country]; - const [stockTable, suspend] = useQueryComponent({ - query: useStockTableInfoQuery(categories[tabIndex], country), - }); - - const handleClick = (name: string) => { - navigate(webPath.search(), { state: { symbolName: name, country: country } }); - }; - - const handleTab = (index: number) => { - if (tabIndex === index) { - return; - } - - setTabIndex(index); - }; - - return ( - - -
- 종목 차트별 -
- 매일 {updateTime}시 업데이트됩니다. -
- - {tabMenu.map((el, index) => ( -
  • handleTab(index)} - > - {el} -
  • - ))} -
    - - - 종목 - 주가 - 인간지표 - - {suspend || - (stockTable && - stockTable.map((stock: StockTableInfo, index: number) => { - return ( - handleClick(stock.symbolName)}> - - - {/* */} - {stock.symbolName} - - - - {stock.price.toLocaleString()} - - {stock.priceDiff > 0 ? '+' : ''} - {stock.priceDiff.toLocaleString()}({Math.abs(stock.priceDiffPerCent)}%) - - - - {stock.score}점 - - ({stock.scoreDiff > 0 ? '+' : ''} - {stock.scoreDiff}) - - - - ); - }))} -
    - ); -}; - -export default StockTable; diff --git a/src/components/Lab/ExperimentItem/ExperimentItem.Style.ts b/src/components/Lab/ExperimentItem/ExperimentItem.Style.ts new file mode 100644 index 00000000..224f4cab --- /dev/null +++ b/src/components/Lab/ExperimentItem/ExperimentItem.Style.ts @@ -0,0 +1,76 @@ +import styled from '@emotion/styled'; +import { deltaToColor } from '@utils/ScoreConvert'; +import { theme } from '@styles/themes'; + +const ExperimentItemContainer = styled.div({ + display: 'flex', + alignItems: 'center', + + ['>p.index']: { + width: '32px', + margin: '0px', + ...theme.font.body14Semibold, + color: theme.colors.sub_blue6, + flexShrink: '0', + textAlign: 'center', + }, + + ['>button.more']: { + ...theme.font.body14Semibold, + background: theme.colors.sub_blue6, + color: theme.colors.sub_white, + borderRadius: '5px', + padding: '4px 16px', + border: 'none', + flexShrink: '0', + }, +}); + +const ExperimentItemContent = styled.div({ + display: 'flex', + padding: '6px 10px', + alignItems: 'center', + gap: '12px', + overflow: 'hidden', + flexGrow: 1, + + ['>img']: { + width: '28px', + height: '28px', + aspectRatio: '1 / 1', + borderRadius: '999px', + objectFit: 'contain', + }, + + ['>div']: { + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + + ['>p']: { + margin: '0', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + + ['&.name']: { + ...theme.font.body14Semibold, + color: theme.colors.sub_gray1, + }, + ['&.date']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray6, + }, + ['&.diff']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray6, + }, + }, + }, +}); + +const ColoredDiffLabel = styled.span(({ delta }: { delta: number }) => ({ + color: deltaToColor(delta) ?? theme.colors.sub_gray7, +})); + +export { ExperimentItemContainer, ExperimentItemContent, ColoredDiffLabel }; diff --git a/src/components/Lab/ExperimentItem/ExperimentItem.tsx b/src/components/Lab/ExperimentItem/ExperimentItem.tsx new file mode 100644 index 00000000..c6fe0446 --- /dev/null +++ b/src/components/Lab/ExperimentItem/ExperimentItem.tsx @@ -0,0 +1,54 @@ +import { getFormattedDate } from '@utils/dateFormatter'; +import StockImage from '@components/Common/StockImage'; +import { ExperimentItem } from '@controllers/experiment/api'; +import { ColoredDiffLabel, ExperimentItemContainer, ExperimentItemContent } from './ExperimentItem.Style'; + +const ExperimentItemComponent = ({ + experiment, + idx, + handleClickExperimentDetail, +}: { + experiment: ExperimentItem; + idx: number; + handleClickExperimentDetail: (experimentId: number) => void; +}) => { + const { stockId, symbolName, buyAt, status, buyPrice, roi } = experiment; + const dateText = getFormattedDate(buyAt); + const statusText = status === 'PROGRESS' ? '실험중' : '완료'; + + const daysAgo = (() => { + // buyAt이 배열 형태인 경우 Date 객체로 변환 + const buyDate = Array.isArray(buyAt) + ? new Date(buyAt[0], buyAt[1] - 1, buyAt[2], buyAt[3] || 0, buyAt[4] || 0, buyAt[5] || 0) + : new Date(buyAt); + return Math.floor((new Date().getTime() - buyDate.getTime()) / (24 * 60 * 60 * 1000)); + })(); + + const diff = roi; + const sign = diff > 0 ? '+' : diff < 0 ? '-' : ''; + const diffPercent = buyPrice !== 0 ? (Math.abs(diff / buyPrice) * 100).toFixed(1) : '0.0'; + const diffPercentText = sign + diffPercent + '%'; + + return ( + +

    {idx + 1}

    + + +
    +

    {symbolName}

    +

    + {dateText}({statusText}) +

    +

    + {daysAgo}일전보다 {diffPercentText} +

    +
    +
    + +
    + ); +}; + +export default ExperimentItemComponent; diff --git a/src/components/Lab/ReportClassChart/ReportClassChart.Style.ts b/src/components/Lab/ReportClassChart/ReportClassChart.Style.ts new file mode 100644 index 00000000..5e1f4d71 --- /dev/null +++ b/src/components/Lab/ReportClassChart/ReportClassChart.Style.ts @@ -0,0 +1,93 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const Container = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '4px', +}); + +const Content = styled.div({ + width: '100%', + height: '200px', + position: 'relative', + + ['>canvas']: { + width: '100%', + height: '100%', + }, +}); + +const TooltipLine = styled.span( + ({ successRate }: { successRate: number }) => ({ + left: `${((1 + (successRate / 100) * 20) / 22) * 100}%`, + }), + { + position: 'absolute', + top: '0', + bottom: '0', + transform: 'translateX(-50%)', + width: '2px', + height: '100%', + background: 'rgba(255, 255, 255, 0.12)', + backgroundImage: `linear-gradient(to bottom, ${theme.colors.sub_white}80 0px, ${theme.colors.sub_blue7} 100%)`, + maskImage: `repeating-linear-gradient(to bottom, #FFFFFF, #FFFFFF 3px, transparent 3px, transparent 6px)`, + webkitMaskImage: `repeating-linear-gradient(to bottom, #FFFFFF, #FFFFFF 3px, transparent 3px, transparent 6px)`, + }, +); + +const TooltipContainer = styled.div( + ({ successRate, width }: { successRate: number; width: number }) => { + return { + left: `clamp(calc(0% + ${width / 2}px), ${successRate}%, calc(100% - ${width / 2}px))`, + }; + }, + { + position: 'absolute', + top: '0', + color: theme.colors.sub_white, + transform: `translateX(-50%)`, + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '10px 16px', + background: 'rgba(255, 255, 255, 0.12)', + border: '1px solid rgba(255, 255, 255, 0.1)', + backdropFilter: 'blur(10px)', + borderRadius: '999px', + whiteSpace: 'nowrap', + + ['>p']: { + margin: '0', + + ['&.title']: { + ...theme.font.detail12Semibold, + color: theme.colors.sub_gray4, + + ['>b']: { + ...theme.font.detail12Semibold, + color: theme.colors.sub_white, + }, + }, + + ['&.description']: { + ...theme.font.detail10Medium, + color: theme.colors.sub_gray4, + }, + }, + }, +); + +const IndexContainer = styled.div({ + display: 'flex', + + ['>span']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + width: '100%', + textAlign: 'center', + }, +}); + +export { Container, Content, TooltipLine, TooltipContainer, IndexContainer }; diff --git a/src/components/Lab/ReportClassChart/ReportClassChart.Type.tsx b/src/components/Lab/ReportClassChart/ReportClassChart.Type.tsx new file mode 100644 index 00000000..0224a059 --- /dev/null +++ b/src/components/Lab/ReportClassChart/ReportClassChart.Type.tsx @@ -0,0 +1,65 @@ +import { theme } from '@styles/themes'; + +export type ReportClassKey = 'worst' | 'bad' | 'normal' | 'good' | 'best'; + +export interface ReportClassType { + icon: string; + title: string; + background: string; + color: string; + min: number; + max: number; +} + +export const reportClassMap: Record = { + worst: { + icon: '😱', + title: '완전 인간 아님', + color: theme.colors.sub_white, + background: theme.colors.sub_red, + min: 0, + max: 20, + }, + bad: { + icon: '🙁', + title: '인간 아님', + color: theme.colors.sub_white, + background: theme.colors.sub_red, + min: 20, + max: 40, + }, + normal: { + icon: '😐', + title: '평범 인간', + color: theme.colors.sub_white, + background: theme.colors.sub_gray9, + min: 40, + max: 60, + }, + good: { + icon: '☺️', + title: '인간 맞음', + color: theme.colors.sub_black, + background: theme.colors.sub_gray1, + min: 60, + max: 80, + }, + best: { + icon: '😆', + title: '인간 완전 맞음', + color: theme.colors.sub_black, + background: theme.colors.sub_white, + min: 80, + max: 100, + }, +}; + +export const reportClassList: ({ + key: ReportClassKey; +} & ReportClassType)[] = Object.entries(reportClassMap).map( + ([key, value]) => + ({ + key, + ...value, + }) as { key: ReportClassKey } & ReportClassType, +); diff --git a/src/components/Lab/ReportClassChart/ReportClassChart.tsx b/src/components/Lab/ReportClassChart/ReportClassChart.tsx new file mode 100644 index 00000000..66cee3e7 --- /dev/null +++ b/src/components/Lab/ReportClassChart/ReportClassChart.tsx @@ -0,0 +1,102 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { theme } from '@styles/themes'; +import { Container, Content, IndexContainer, TooltipContainer, TooltipLine } from './ReportClassChart.Style'; +import { ReportClassType } from './ReportClassChart.Type'; + +const ReportClassChart = ({ + reportClass, + successRate, + sameGradeUserRate, +}: { + reportClass: ReportClassType; + successRate: number; + sameGradeUserRate?: number; +}) => { + const canvasRef = useRef(null); + const tooltipRef = useRef(null); + + const [tooltipWidth, setTooltipWidth] = useState(0); + + useEffect(() => { + if (tooltipRef.current) { + setTooltipWidth(tooltipRef.current.clientWidth); + } + }, [tooltipRef]); + + const fx = (x: number, mu: number, sigma: number) => { + return Math.exp(-Math.pow((x - mu) / sigma, 2) / 2) / (sigma * Math.sqrt(2 * Math.PI)); + }; + + const drawChart = useCallback( + (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement) => { + const { width, height } = canvas; + + ctx.clearRect(0, 0, width, height); + ctx.lineWidth = 2; + ctx.strokeStyle = theme.colors.sub_blue7; + + ctx.beginPath(); + for (let x = 0; x <= 100; x += 1) { + const y = fx(x, 50, 20); + const pos: [number, number] = [(x * width) / 100, (1 - y / 0.025) * height]; + ctx[x === 0 ? 'moveTo' : 'lineTo'](...pos); + } + ctx.stroke(); + + ctx.lineTo(width, height); + ctx.lineTo(0, height); + + ctx.closePath(); + + const gradient = ctx.createLinearGradient(0, 0, 0, height); + gradient.addColorStop(0, theme.colors.sub_blue5); + gradient.addColorStop(1, `${theme.colors.sub_blue5}00`); + ctx.fillStyle = gradient; + + ctx.fill(); + }, + [canvasRef], + ); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) { + return; + } + + const ctx = canvas.getContext('2d'); + if (!ctx) { + return; + } + + drawChart(ctx, canvas); + }, []); + + const { min, max } = reportClass; + + return ( + + + + + +

    + 성공률{' '} + + {min} + {max === 100 ? '% 이상' : `~${max}%`} + +

    + {sameGradeUserRate &&

    (전체 유저 중 {sameGradeUserRate.toFixed(1)}%)

    } +
    +
    + + {Array.from({ length: 11 }).map((_, idx) => ( + {idx} + ))} + +
    + ); +}; + +export default ReportClassChart; diff --git a/src/components/Lab/ReportPatternChart/ReportPatternChart.Style.ts b/src/components/Lab/ReportPatternChart/ReportPatternChart.Style.ts new file mode 100644 index 00000000..ed84d4c3 --- /dev/null +++ b/src/components/Lab/ReportPatternChart/ReportPatternChart.Style.ts @@ -0,0 +1,166 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; +import { PatternQuadrantKey } from './ReportPatternChart.Type'; + +const ReportPatternChartContainer = styled.div({ + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + + ['>div']: { + position: 'relative', + height: '200px', + width: '100%', + + ['>canvas']: { + width: '100%', + height: '100%', + }, + }, +}); + +const ReportPatternChartAxisLabel = styled.p( + ({ isTutorial }: { isTutorial?: boolean }) => ({ + ['&.roi']: { + color: isTutorial ? theme.colors.sub_blue6 : theme.colors.sub_gray6, + }, + + ['&.index']: { + color: isTutorial ? theme.colors.sub_red : theme.colors.sub_gray6, + }, + }), + { + margin: '0px', + ...theme.font.body16Semibold, + whiteSpace: 'nowrap', + + ['&.index']: { + position: 'absolute', + right: '0', + top: 'calc(50% + 8px)', + }, + }, +); + +const ReportPatternChartItem = styled.div( + ({ x, y, quadrant }: { x: number; y: number; quadrant: PatternQuadrantKey }) => { + const inQuadrant = + x - 50 >= 0 + ? y - 50 >= 0 + ? quadrant === 'trend-preemptive' + : quadrant === 'lagging-follower' + : y - 50 >= 0 + ? quadrant === 'value-preemptive' + : quadrant === 'reverse-investor'; + + return { + left: `${x}%`, + bottom: `${y}%`, + + ['>p']: { + color: inQuadrant ? theme.colors.sub_gray2 : theme.colors.sub_gray7, + }, + }; + }, + { + position: 'absolute', + width: '0px', + height: '0px', + + ['>p']: { + margin: '0', + position: 'absolute', + bottom: 'calc(50% + 16px)', + left: '50%', + transform: 'translate(-50%, 50%)', + ...theme.font.detail12Medium, + }, + + ['>span']: { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '10px', + height: '10px', + borderRadius: '50%', + background: theme.colors.sub_blue6, + }, + }, +); + +const ReportPatternChartQuadrant = styled.div( + ({ quadrant }: { quadrant: PatternQuadrantKey }) => ({ + top: ['trend-preemptive', 'value-preemptive'].includes(quadrant) ? '0' : '', + left: ['value-preemptive', 'reverse-investor'].includes(quadrant) ? '0' : '', + right: ['trend-preemptive', 'lagging-follower'].includes(quadrant) ? '0' : '', + bottom: ['reverse-investor', 'lagging-follower'].includes(quadrant) ? '0' : '', + }), + { + position: 'absolute', + width: 'calc(50% - 6px)', + height: 'calc(50% - 6px)', + opacity: 0.2, + padding: '6px', + boxSizing: 'border-box', + background: theme.colors.sub_blue6, + borderRadius: '4px', + }, +); + +const ReportPatternChartTutorialQuadrant = styled.div({ + position: 'absolute', + width: '100%', + height: '100%', + top: '0', + left: '0', + padding: '16px 24px', + boxSizing: 'border-box', + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gridTemplateRows: '1fr 1fr', + + ['>div']: { + display: 'flex', + flexDirection: 'column', + gap: '8px', + alignItems: 'start', + justifyContent: 'center', + width: '120px', + margin: 'auto', + + ['>div']: { + display: 'flex', + flexDirection: 'column', + + ['>p']: { + margin: '0', + ['&.title']: { + ...theme.font.detail12Semibold, + color: theme.colors.sub_white, + }, + ['&.description']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray6, + }, + }, + }, + + ['>span']: { + ...theme.font.detail10Semibold, + color: theme.colors.sub_white, + padding: '4px 8px', + background: theme.colors.sub_gray9, + borderRadius: '999px', + }, + }, +}); + +export { + ReportPatternChartContainer, + ReportPatternChartAxisLabel, + ReportPatternChartItem, + ReportPatternChartQuadrant, + ReportPatternChartTutorialQuadrant, +}; diff --git a/src/components/Lab/ReportPatternChart/ReportPatternChart.Type.tsx b/src/components/Lab/ReportPatternChart/ReportPatternChart.Type.tsx new file mode 100644 index 00000000..73abb73f --- /dev/null +++ b/src/components/Lab/ReportPatternChart/ReportPatternChart.Type.tsx @@ -0,0 +1,77 @@ +export type PatternQuadrantKey = 'trend-preemptive' | 'lagging-follower' | 'reverse-investor' | 'value-preemptive'; + +export const patternQuadrantKeys: PatternQuadrantKey[] = [ + 'trend-preemptive', + 'lagging-follower', + 'reverse-investor', + 'value-preemptive', +]; + +export interface PatternQuadrant { + emoji: string; + title: string; + score: string; + roi: string; + description: React.ReactElement; +} + +export const patternQuadrantMap: Record = { + 'trend-preemptive': { + emoji: '✅', + title: '트렌드 선점형', + score: '인간지표 높을 때 매수', + roi: '수익', + description: ( + <> + 투자자들의 관심도가 높을 때 매수하여 수익을 보는 투자 패턴
    = 트렌드 형성 시점에 선제적으로 대응하는 투자 + 성향을 보이고 있네요! + + ), + }, + 'lagging-follower': { + emoji: '❕', + title: '후행 추종형', + score: '인간지표 높을 때 매수', + roi: '손실', + description: ( + <> + 투자자들의 관심도가 높을 때 매수하여 손실을 보는 투자 패턴
    = 과열 국면에서 진입해 변동성 영향을 크게 받는 + 투자 성향을 보이고 있네요! + + ), + }, + 'reverse-investor': { + emoji: '📉', + title: '역행 투자형', + score: '인간지표 낮을 때 매수', + roi: '손실', + description: ( + <> + 투자자들의 관심도가 낮을 때 매수하여 손실을 보는 투자 패턴
    + =진입 시점하는 타이밍이 시장 흐름과 맞지 않는 경우가 나타나고 있네요! + + ), + }, + 'value-preemptive': { + emoji: '💎', + title: '가치 선점형', + score: '인간지표 낮을 때 매수', + roi: '수익', + description: ( + <> + 투자자들의 관심도가 낮을 때 매수하여 수익을 보는 투자 패턴
    + =저평가 구간에서 기회를 선점하는 투자 성향을 보이고 있네요! + + ), + }, +}; + +export const patternQuadrantList: ({ + key: PatternQuadrantKey; +} & PatternQuadrant)[] = Object.entries(patternQuadrantMap).map( + ([key, value]) => + ({ + key, + ...value, + }) as { key: PatternQuadrantKey } & PatternQuadrant, +); diff --git a/src/components/Lab/ReportPatternChart/ReportPatternChart.tsx b/src/components/Lab/ReportPatternChart/ReportPatternChart.tsx new file mode 100644 index 00000000..30560fcd --- /dev/null +++ b/src/components/Lab/ReportPatternChart/ReportPatternChart.tsx @@ -0,0 +1,442 @@ +import styled from '@emotion/styled'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { getDiffText } from '@utils/Number'; +import { deltaToColor } from '@utils/ScoreConvert'; +import { PortfolioResultPatternHistory } from '@controllers/experiment/api'; +import { theme } from '@styles/themes'; +import SpeechBubbleSVG from '@assets/design/speechBubble.svg?react'; +import AimSVG from '@assets/icons/aim.svg?react'; +import { ReportPatternChartTutorialQuadrant } from './ReportPatternChart.Style'; +import { PatternQuadrantKey, patternQuadrantList } from './ReportPatternChart.Type'; + +const ChartCanvas = styled.canvas({ + position: 'absolute', + top: '0', + left: '0', + width: '100%', + height: '100%', + bottom: '0', + right: '0', + borderRadius: '4px', +}); + +const ChartAxisLabelContainer = styled.div({ + position: 'absolute', + left: '4px', + top: '4px', + right: '4px', + bottom: '4px', +}); + +const ChartAxisLabel = styled.div( + ({ position }: { position: 'top' | 'bottom' | 'left' | 'right' }) => ({ + left: position === 'left' ? '0' : position === 'right' ? 'auto' : '50%', + top: position === 'top' ? '0' : position === 'bottom' ? 'auto' : '50%', + right: position === 'right' ? '0' : position === 'left' ? 'auto' : '50%', + bottom: position === 'bottom' ? '0' : position === 'top' ? 'auto' : '50%', + + ['>p']: { + rotate: position === 'left' ? '-90deg' : position === 'right' ? '90deg' : '0deg', + transform: `translateY(${position === 'top' ? '50%' : position === 'bottom' ? '-50%' : '0'}) translateY(${position === 'left' || position === 'right' ? '50%' : '0'})`, + }, + }), + { + position: 'absolute', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + + ['>p']: { + ...theme.font.detail10Medium, + color: theme.colors.sub_gray7, + position: 'absolute', + margin: '0', + whiteSpace: 'nowrap', + }, + }, +); + +const ChartInduceContainer = styled.div({ + position: 'absolute', + right: '0', + bottom: '0', + display: 'flex', + alignItems: 'center', + gap: '4px', + background: theme.colors.sub_black, + padding: '2px 8px', + borderRadius: '10px', + margin: '8px', + + ['>p']: { + ...theme.font.detail10Medium, + color: theme.colors.sub_gray9, + margin: '0', + }, + + ['>svg']: { + width: '10px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray9, + }, +}); + +const ChartSelectedItemContainer = styled.div( + ({ pos }: { pos: { x: number; y: number } }) => ({ + bottom: pos.y, + left: pos.x, + }), + { + position: 'absolute', + zIndex: '2', + }, +); + +const ChartSelectedItemContent = styled.div( + ({ delta }: { delta: number }) => ({ + ['p.roi']: { + color: deltaToColor(delta) ?? theme.colors.sub_gray4, + }, + }), + { + position: 'absolute', + display: 'flex', + flexDirection: 'column', + + width: '112px', + + padding: '6px 12px', + boxSizing: 'border-box', + borderRadius: '5px', + + transform: 'translateX(-50%)', + bottom: '24px', + + ['p']: { + margin: '0', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + + ['>p']: { + ...theme.font.detail12Semibold, + color: theme.colors.sub_gray4, + }, + + ['>div']: { + display: 'flex', + flexDirection: 'column', + + ['>div']: { + display: 'flex', + justifyContent: 'space-between', + + ['>p']: { + ['&.name']: { + color: theme.colors.sub_gray7, + }, + + ['&.value']: { + color: theme.colors.sub_gray4, + }, + }, + }, + ['p']: { + ...theme.font.detail10Medium, + }, + }, + + ['>svg']: { + position: 'absolute', + top: '0', + left: '0', + zIndex: '-1', + }, + }, +); + +const ChartDotsContainer = styled.div({ + position: 'absolute', + top: '0', + left: '0', + width: '100%', + height: '100%', +}); + +const ChartDot = styled.div( + ({ + pos, + delta, + selected, + isOpaque, + }: { + pos: { x: number; y: number }; + delta: number; + selected: boolean; + isOpaque: boolean; + }) => ({ + left: pos.x, + bottom: pos.y, + opacity: isOpaque ? '0.5' : '1', + + ['>span']: { + background: deltaToColor(delta) ?? theme.colors.sub_gray4, + width: selected ? '16px' : '10px', + border: selected ? `1px solid ${theme.colors.sub_white}` : 'none', + boxShadow: selected ? `0 0 0 4px ${theme.colors.sub_white}33` : 'none', + }, + }), + { + position: 'absolute', + + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + transition: 'opacity 0.1s ease-in-out', + + ['>span']: { + borderRadius: '50%', + position: 'absolute', + height: 'auto', + aspectRatio: '1 / 1', + transition: 'width 0.2s ease-in-out, box-shadow 0.2s ease-in-out', + zIndex: '1', + }, + + ['>p']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray6, + position: 'absolute', + whiteSpace: 'nowrap', + left: '50%', + transform: 'translateX(-50%)', + top: '8px', + margin: '0', + maxWidth: '64px', + overflow: 'hidden', + textOverflow: 'ellipsis', + textAlign: 'center', + zIndex: '0', + userSelect: 'none', + }, + }, +); + +const ReportPatternChart = ({ + type = 'value-preemptive', + history, + isTutorial = false, +}: { + type?: PatternQuadrantKey; + history?: PortfolioResultPatternHistory[]; + isTutorial?: boolean; +}) => { + const canvasRef = useRef(null); + const [canvasSize, setCanvasSize] = useState<{ width: number; height: number }>({ width: 0, height: 0 }); + + const historyItems = useMemo(() => { + if (!history?.length) return []; + + const { width, height } = canvasSize; + + let minRoi = Infinity, + maxRoi = -Infinity, + minScore = Infinity, + maxScore = -Infinity; + + history.forEach((e) => { + if (e.roi <= 0 && e.roi < minRoi) minRoi = e.roi; + if (e.roi >= 0 && e.roi > maxRoi) maxRoi = e.roi; + if (e.score <= 50 && e.score < minScore) minScore = e.score; + if (e.score >= 50 && e.score > maxScore) maxScore = e.score; + }); + + const yieldScale = height / 2 - 36; + const scoreScale = width / 2 - 36; + + return history.map((e, idx) => ({ + ...e, + pos: { + x: + e.score < 50 + ? -((e.score - 50) / (minScore - 50)) * scoreScale + width / 2 + : ((e.score - 50) / (maxScore - 50)) * scoreScale + width / 2, + y: e.roi < 0 ? -(e.roi / minRoi) * yieldScale + height / 2 : (e.roi / maxRoi) * yieldScale + height / 2, + }, + idx, + })); + }, [history, canvasSize]); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + const { width, height } = canvasSize; + const DPR = window.devicePixelRatio; + canvas.width = width * DPR; + canvas.height = height * DPR; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.setTransform(window.devicePixelRatio, 0, 0, window.devicePixelRatio, 0, 0); + + ctx.fillStyle = theme.colors.sub_blue10; + + const x = type === 'trend-preemptive' || type === 'lagging-follower' ? width / 2 : 0; + const y = type === 'reverse-investor' || type === 'lagging-follower' ? height / 2 : 0; + ctx.fillRect(x, y, width / 2, height / 2); + ctx.strokeStyle = theme.colors.sub_gray10; + ctx.lineWidth = 1; + + ctx.beginPath(); + ctx.moveTo(width / 2, 20); + ctx.lineTo(width / 2, height - 20); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(20, height / 2); + ctx.lineTo(width - 20, height / 2); + ctx.stroke(); + }, [canvasRef, canvasSize]); + + const [selectedItem, setSelectedItem] = useState<{ + date: string; + score: number; + roi: number; + stockId: number; + stockName: string; + duplicateName: boolean; + pos: { + x: number; + y: number; + }; + idx: number; + }>(); + + useEffect(() => { + const container = canvasRef.current; + if (!container) return; + + const resizeObserver = new ResizeObserver((entries) => { + const entry = entries[0]; + if (!entry) return; + + const { width, height } = entry.contentRect; + setCanvasSize({ width, height }); + }); + + resizeObserver.observe(container); + + return () => { + resizeObserver.disconnect(); + }; + }, []); + + const axisLabels: { text: string; position: 'top' | 'bottom' | 'left' | 'right' }[] = [ + { + text: '수익률 High', + position: 'top', + }, + { + text: '수익률 Low', + position: 'bottom', + }, + { + text: '인간지표 Low', + position: 'left', + }, + { + text: '인간지표 High', + position: 'right', + }, + ]; + + return ( +
    + + + {axisLabels.map((e) => ( + +

    {e.text}

    +
    + ))} +
    + {!isTutorial ? ( + <> + + {historyItems.map((e, idx) => ( + + { + setSelectedItem((prev) => (prev?.idx === e.idx ? undefined : e)); + }} + /> +

    {e.stockName}

    +
    + ))} +
    + {selectedItem && ( + + + +

    {selectedItem.stockName}

    +
    +
    +

    수익률

    +

    {getDiffText({ percentDiff: selectedItem.roi, option: { percentFixed: 1 } })}

    +
    +
    +

    인간지표

    +

    {selectedItem.score}점

    +
    +
    +

    매수일

    +

    XX.{selectedItem.date}

    +
    +
    +
    +
    + )} + + +

    점을 터치해보세요

    +
    + + ) : ( + + {patternQuadrantList.map((e) => ( +
    +
    +

    + {e.emoji} {e.title} +

    +

    {e.score}

    +
    + → {e.roi} +
    + ))} +
    + )} +
    + ); +}; + +export default ReportPatternChart; diff --git a/src/components/Loading/Loading.Style.ts b/src/components/Loading/Loading.Style.ts new file mode 100644 index 00000000..6c38276f --- /dev/null +++ b/src/components/Loading/Loading.Style.ts @@ -0,0 +1,67 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const LoadingContainer = styled.div( + ({ top, bottom }: { top?: string; bottom?: string }) => ({ + top: top ?? 0, + bottom: bottom ?? 0, + }), + { + position: 'fixed', + bottom: '96px', + left: 0, + right: 0, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + background: 'linear-gradient(180deg, rgba(16, 16, 16, 0.4) 0%, #101010DD 81.02%)', + backdropFilter: 'blur(2.5px)', // Note: backdrop-filter has minimal browser support + zIndex: 9999, + + ['>svg']: { + position: 'absolute', + width: '110%', + height: 'auto', + right: '0', + top: '0', + }, + }, +); + +const LoadingContent = styled.div({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + gap: '16px', + + ['>img']: { + width: '80px', + height: 'auto', + aspectRatio: '1 / 1', + }, + + ['>div']: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + gap: '8px', + + ['>p']: { + margin: '0', + + ['&.title']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_gray4, + }, + + ['&.desc']: { + ...theme.font.body16Regular, + color: theme.colors.sub_gray6, + }, + }, + }, +}); + +export { LoadingContainer, LoadingContent }; diff --git a/src/components/Loading/Loading.tsx b/src/components/Loading/Loading.tsx new file mode 100644 index 00000000..1f869259 --- /dev/null +++ b/src/components/Loading/Loading.tsx @@ -0,0 +1,24 @@ +import BackgroundSVG from '@assets/background.svg?react'; +import LoadingGIF from '@assets/loading.gif'; +import { LoadingContainer, LoadingContent } from './Loading.Style'; + +const Loading = ({ isLoading, title, desc }: { isLoading?: boolean; title: string; desc?: string }) => { + if (!isLoading) { + return null; + } + + return ( + + + + +
    +

    {title}

    + {desc &&

    {desc}

    } +
    +
    +
    + ); +}; + +export default Loading; diff --git a/src/components/Modal/AboutReportClass/AboutReportClass.Style.ts b/src/components/Modal/AboutReportClass/AboutReportClass.Style.ts new file mode 100644 index 00000000..bcbf9950 --- /dev/null +++ b/src/components/Modal/AboutReportClass/AboutReportClass.Style.ts @@ -0,0 +1,107 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const Container = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '16px', +}); + +const Header = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '4px', + padding: '14px 10px', + borderBottom: `1px solid ${theme.colors.sub_gray10}`, + margin: '0 20px', + + ['>p']: { + margin: '0', + wordBreak: 'keep-all', + + ['&.title']: { + ...theme.font.body18Semibold, + color: theme.colors.sub_gray1, + + ['>svg']: { + width: '24px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_white, + flexShrink: '0', + verticalAlign: 'middle', + }, + }, + + ['&.description']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray7, + }, + }, +}); + +const TabContainer = styled.div({ + display: 'flex', + gap: '16px', + overflow: 'auto', + padding: '0 20px', + + msOverflowStyle: 'none', + ['&::-webkit-scrollbar']: { + display: 'none', + }, +}); + +const TabItem = styled.span( + ({ isSelected }: { isSelected?: boolean }) => ({ + background: isSelected ? theme.colors.sub_blue6 : theme.colors.sub_gray10, + color: isSelected ? theme.colors.sub_white : theme.colors.sub_gray6, + }), + { + position: 'relative', + ...theme.font.body14Medium, + whiteSpace: 'nowrap', + padding: '8px 16px', + borderRadius: '999px', + }, +); + +const Content = styled.div({ + display: 'flex', + flexDirection: 'column', + padding: '20px 10px 12px', + margin: '0 20px', + background: theme.colors.sub_gray11, + borderRadius: '4px', + gap: '20px', + + ['>span.divider']: { + width: '100%', + height: '1px', + background: theme.colors.sub_gray10, + }, +}); + +const ContentHeader = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '8px', + padding: '0 8px', + + ['>p']: { + margin: '0', + + ['&.title']: { + ...theme.font.body14Semibold, + color: theme.colors.sub_gray2, + }, + ['&.description']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray5, + lineHeight: '175%', + whiteSpace: 'pre-line', + }, + }, +}); + +export { Container, Header, TabContainer, TabItem, Content, ContentHeader }; diff --git a/src/components/Modal/AboutReportClass/AboutReportClass.tsx b/src/components/Modal/AboutReportClass/AboutReportClass.tsx new file mode 100644 index 00000000..6b5ed311 --- /dev/null +++ b/src/components/Modal/AboutReportClass/AboutReportClass.tsx @@ -0,0 +1,59 @@ +import { useState } from 'react'; +import ReportClassChart from '@components/Lab/ReportClassChart/ReportClassChart'; +import { + ReportClassKey, + reportClassList, + reportClassMap, +} from '@components/Lab/ReportClassChart/ReportClassChart.Type'; +import { usePortfolioResultQuery } from '@controllers/experiment/query'; +import QuestionMarkSVG from '@assets/icons/questionMark.svg?react'; +import { Container, Content, ContentHeader, Header, TabContainer, TabItem } from './AboutReportClass.Style'; + +const AboutReportClass = () => { + const { data: portfolioResult } = usePortfolioResultQuery(); + const [isSelected, setIsSelected] = useState('worst'); + + if (!portfolioResult) return null; + const { humanIndicator } = portfolioResult; + const { distribution } = humanIndicator; + + const selectedDistribution = distribution[isSelected]; + + const { min, max } = reportClassMap[isSelected]; + + return ( + +
    +

    + 다른 '인간지표' 유형은 뭐가 있어요? +

    +

    + 실험이 끝났을 때 수익률이 0이상인 실험을,
    + 성공한 실험으로 보고 있어요 +

    +
    + + {reportClassList.map((e) => ( + setIsSelected(e.key)} isSelected={isSelected === e.key}> + {e.icon} {e.title} + + ))} + + + +

    {reportClassMap[isSelected].title} 지표란?

    +

    + 성공률이 {min} + {max === 100 ? '% 이상' : `~${max}%`}인 유형을 말해요 +
    + 유저 중 {selectedDistribution}%가 이에 속한답니다 +

    +
    + + +
    +
    + ); +}; + +export default AboutReportClass; diff --git a/src/components/Modal/AboutReportClass/useAboutReportClass.ts b/src/components/Modal/AboutReportClass/useAboutReportClass.ts new file mode 100644 index 00000000..dc2e4609 --- /dev/null +++ b/src/components/Modal/AboutReportClass/useAboutReportClass.ts @@ -0,0 +1,20 @@ +import BottomUpCancel from '../Layout/BottomUpCancel/BottomUpCancel'; +import useModal from '../useModal'; +import AboutReportClass from './AboutReportClass'; + +const useAboutReportClass = (): { + openModal: () => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: BottomUpCancel, + Component: AboutReportClass, + modalKey: 'aboutReportClass', + showDelay: 200, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useAboutReportClass; diff --git a/src/components/Modal/AboutReportPattern/AboutReportPattern.Style.ts b/src/components/Modal/AboutReportPattern/AboutReportPattern.Style.ts new file mode 100644 index 00000000..c5cb395b --- /dev/null +++ b/src/components/Modal/AboutReportPattern/AboutReportPattern.Style.ts @@ -0,0 +1,106 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const Container = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '16px', +}); + +const Header = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '16px', + padding: '14px 10px', + margin: '0 20px', + + ['>p']: { + margin: '0', + wordBreak: 'keep-all', + + ['&.title']: { + ...theme.font.body18Semibold, + color: theme.colors.sub_gray1, + + ['>svg']: { + width: '24px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_white, + flexShrink: '0', + verticalAlign: 'middle', + }, + }, + }, + + ['>span.divider']: { + width: '100%', + height: '1px', + background: theme.colors.sub_gray10, + }, +}); + +const HeaderContents = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '8px', +}); + +const HeaderContentsItem = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '16px', + + ['>span']: { + width: '72px', + padding: '4px', + borderRadius: '999px', + background: theme.colors.sub_gray11, + ...theme.font.body14Semibold, + textAlign: 'center', + + ['&.roi']: { + color: theme.colors.sub_blue6, + }, + ['&.score']: { + color: theme.colors.sub_red, + }, + }, + + ['>div']: { + display: 'flex', + flexDirection: 'column', + + ['>div']: { + display: 'flex', + alignItems: 'center', + gap: '10px', + + ['>span']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray5, + whiteSpace: 'nowrap', + ['&.condition']: { + width: '72px', + }, + }, + }, + }, +}); + +const Content = styled.div({ + display: 'flex', + flexDirection: 'column', + margin: '0 20px', + background: theme.colors.sub_gray11, + borderRadius: '4px', + gap: '20px', + + ['>span.divider']: { + width: '100%', + height: '1px', + background: theme.colors.sub_gray10, + }, +}); + +export { Container, Header, HeaderContents, HeaderContentsItem, Content }; diff --git a/src/components/Modal/AboutReportPattern/AboutReportPattern.tsx b/src/components/Modal/AboutReportPattern/AboutReportPattern.tsx new file mode 100644 index 00000000..4c225c38 --- /dev/null +++ b/src/components/Modal/AboutReportPattern/AboutReportPattern.tsx @@ -0,0 +1,55 @@ +import ReportPatternChart from '@components/Lab/ReportPatternChart/ReportPatternChart'; +import QuestionMarkSVG from '@assets/icons/questionMark.svg?react'; +import { Container, Content, Header, HeaderContents, HeaderContentsItem } from './AboutReportPattern.Style'; + +const patternReads = [ + { + key: 'roi', + text: '수익률', + items: [ + { key: 'success', condition: '0점 위쪽', text: '성공 🤗' }, + { key: 'failure', condition: '0점 아래쪽', text: '실패 😭' }, + ], + }, + { + key: 'score', + text: '인간지표', + items: [ + { key: 'success', condition: '50점 왼쪽', text: '성공 🤗' }, + { key: 'failure', condition: '50점 오른쪽', text: '실패 😭' }, + ], + }, +]; + +const AboutReportPattern = () => { + return ( + +
    +

    + 각 사분면은 무슨 패턴이에요? +

    + + + {patternReads.map((e1) => ( + + {e1.text} +
    + {e1.items.map((e2) => ( +
    + {e2.condition} + → 실험 {e2.text} +
    + ))} +
    +
    + ))} +
    +
    + + + +
    + ); +}; + +export default AboutReportPattern; diff --git a/src/components/Modal/AboutReportPattern/useAboutReportPattern.ts b/src/components/Modal/AboutReportPattern/useAboutReportPattern.ts new file mode 100644 index 00000000..e406d4c5 --- /dev/null +++ b/src/components/Modal/AboutReportPattern/useAboutReportPattern.ts @@ -0,0 +1,20 @@ +import BottomUpCancel from '../Layout/BottomUpCancel/BottomUpCancel'; +import useModal from '../useModal'; +import AboutReportPattern from './AboutReportPattern'; + +const useAboutReportPattern = (): { + openModal: () => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: BottomUpCancel, + Component: AboutReportPattern, + modalKey: 'aboutReportPattern', + showDelay: 200, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useAboutReportPattern; diff --git a/src/components/Modal/CenterTutorial/AboutAntVoice/AboutAntVoice.Style.ts b/src/components/Modal/CenterTutorial/AboutAntVoice/AboutAntVoice.Style.ts new file mode 100644 index 00000000..12baac10 --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutAntVoice/AboutAntVoice.Style.ts @@ -0,0 +1,21 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const AntVoiceImage = styled.img({ + width: 'auto', + objectFit: 'cover', + margin: '0 16px', + boxSizing: 'border-box', +}); + +const AntVoiceDescription = styled.div({ + ...theme.font.detail12Semibold, + color: theme.colors.sub_gray11, + padding: '16px 12px', + background: theme.colors.sub_white, + borderRadius: '8px', + margin: '0 16px', + wordBreak: 'keep-all', +}); + +export { AntVoiceImage, AntVoiceDescription }; diff --git a/src/components/Modal/CenterTutorial/AboutAntVoice/AboutAntVoice.tsx b/src/components/Modal/CenterTutorial/AboutAntVoice/AboutAntVoice.tsx new file mode 100644 index 00000000..350ae44b --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutAntVoice/AboutAntVoice.tsx @@ -0,0 +1,22 @@ +import AntVoicePNG from '@assets/design/antVoice.png'; +import { ModalContainer, ModalContent, ModalTitleContainer } from '../CenterTutotial.Style'; +import { AntVoiceDescription, AntVoiceImage } from './AboutAntVoice.Style'; + +const AboutAntVoice = () => { + return ( + + +

    자주 언급되는 단어란?

    +
    + + + + 각종 커뮤니티 댓글을 한눈에 볼 수 있는 워드클라우드입니다. 크기가 클수록 각종 커뮤니티에서 가장 많이 언급된 + 단어입니다. + + +
    + ); +}; + +export default AboutAntVoice; diff --git a/src/components/Modal/CenterTutorial/AboutAntVoice/useAboutAntVoice.tsx b/src/components/Modal/CenterTutorial/AboutAntVoice/useAboutAntVoice.tsx new file mode 100644 index 00000000..e136de2c --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutAntVoice/useAboutAntVoice.tsx @@ -0,0 +1,20 @@ +import useModal from '@components/Modal/useModal'; +import CenterTutorialLayout from '../Layout'; +import AboutAntVoice from './AboutAntVoice'; + +const useAboutAntVoice = (): { + openModal: () => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: CenterTutorialLayout, + Component: AboutAntVoice, + modalKey: 'aboutAntVoice', + showDelay: 100, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useAboutAntVoice; diff --git a/src/components/Modal/CenterTutorial/AboutCardList/AboutCardList.tsx b/src/components/Modal/CenterTutorial/AboutCardList/AboutCardList.tsx new file mode 100644 index 00000000..fea30c31 --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutCardList/AboutCardList.tsx @@ -0,0 +1,34 @@ +import { ModalContainer, ModalContent, ModalDescriptionContainer, ModalTitleContainer } from '../CenterTutotial.Style'; +import { AboutCardListModalData } from './useAboutCardList'; + +const CardListText = { + HOT: { + title: '👑 현재 시장 반응 TOP 3', + description: '한국투자증권’ 순위를 기반으로 집계됩니다.', + }, + RISING: { + title: '🔥 현재 민심 급상승 중', + description: '현재 각종 주식 커뮤니티에서 투자자들의 민심이 긍정적으로 급상승 중인 종목입니다.', + }, + DESCENT: { + title: '💧 현재 민심 급하락 중', + description: '현재 각종 주식 커뮤니티에서 투자자들의 민심이 부정적으로 급하락 중인 종목입니다.', + }, +}; + +const AboutCardList = ({ modalData: { type } }: { modalData: AboutCardListModalData }) => { + return ( + + +

    {CardListText[type].title}

    +
    + + +

    {CardListText[type].description}

    +
    +
    +
    + ); +}; + +export default AboutCardList; diff --git a/src/components/Modal/CenterTutorial/AboutCardList/useAboutCardList.tsx b/src/components/Modal/CenterTutorial/AboutCardList/useAboutCardList.tsx new file mode 100644 index 00000000..96f0b0a4 --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutCardList/useAboutCardList.tsx @@ -0,0 +1,28 @@ +import useModal from '@components/Modal/useModal'; +import CenterTutorialLayout from '../Layout'; +import AboutCardList from './AboutCardList'; + +export type CardListType = 'HOT' | 'RISING' | 'DESCENT'; + +export interface AboutCardListModalData { + type: CardListType; +} + +const useAboutCardList = ( + type: CardListType, +): { + openModal: (modalData: AboutCardListModalData) => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: CenterTutorialLayout, + Component: AboutCardList, + modalKey: `aboutCardList_${type}`, + showDelay: 100, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useAboutCardList; diff --git a/src/components/Modal/CenterTutorial/AboutFear/AboutFear.tsx b/src/components/Modal/CenterTutorial/AboutFear/AboutFear.tsx new file mode 100644 index 00000000..5fe65c8a --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutFear/AboutFear.tsx @@ -0,0 +1,22 @@ +import { ModalContainer, ModalContent, ModalDescriptionContainer, ModalTitleContainer } from '../CenterTutotial.Style'; + +const AboutFear = () => { + return ( + + +

    공포탐욕지수란?

    +
    + + +

    + 공포탐욕지수는 시장의 7가지 요인을 분석하여 + 현재 투자자들의 심리를 극단적인 공포(0)부터 극단적인 탐욕(100)에 이르기까지를 가늠하는 + 심리지표입니다. +

    +
    +
    +
    + ); +}; + +export default AboutFear; diff --git a/src/components/Modal/CenterTutorial/AboutFear/useAboutFear.tsx b/src/components/Modal/CenterTutorial/AboutFear/useAboutFear.tsx new file mode 100644 index 00000000..fff222cf --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutFear/useAboutFear.tsx @@ -0,0 +1,20 @@ +import useModal from '@components/Modal/useModal'; +import CenterTutorialLayout from '../Layout'; +import AboutFear from './AboutFear'; + +const useAboutFear = (): { + openModal: () => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: CenterTutorialLayout, + Component: AboutFear, + modalKey: 'aboutFear', + showDelay: 100, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useAboutFear; diff --git a/src/components/Modal/CenterTutorial/AboutHumanZipyo/AboutHumanZipyo.Style.ts b/src/components/Modal/CenterTutorial/AboutHumanZipyo/AboutHumanZipyo.Style.ts new file mode 100644 index 00000000..f29d1d0e --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutHumanZipyo/AboutHumanZipyo.Style.ts @@ -0,0 +1,151 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const HumanZipyoDescription = styled.div({ + ...theme.font.body14Semibold, + color: theme.colors.sub_gray10, + margin: '0 16px', + wordBreak: 'keep-all', +}); + +const HumanZipyoGuageChart = styled.div({ + gap: '8px', + display: 'flex', + flexDirection: 'column', + boxSizing: 'border-box', + justifyContent: 'center', + alignItems: 'center', + overflow: 'hidden', + + ['>div']: { + ['&.guage-chart']: { + width: '100%', + left: '50%', + marginTop: '-5%', + }, + + ['&.score-text']: { + display: 'flex', + padding: '0px 28px', + width: '100%', + boxSizing: 'border-box', + }, + + ['&.score-range']: { + display: 'flex', + gap: '4px', + padding: '0px 24px', + width: '100%', + boxSizing: 'border-box', + }, + }, +}); + +const HumanZipyoScoreText = styled.div( + ({ index }: { index: number }) => { + const backgroundColor = ['#11193E', '#121C46', '#141F53', '#1F359B', '#304CD1'][index]; + + return { + background: backgroundColor, + }; + }, + { + ...theme.font.detail10Medium, + color: theme.colors.sub_gray2, + textAlign: 'center', + width: '100%', + margin: '0px', + whiteSpace: 'nowrap', + minWidth: '0', + + ['@media (max-width: 360px)']: { + fontSize: '8px', + }, + }, +); + +const HumanZipyoScoreRange = styled.span({ + ...theme.font.detail10Medium, + color: theme.colors.sub_gray9, + textAlign: 'center', + width: '100%', + margin: '0px', + background: theme.colors.sub_white, + borderRadius: '2px', + position: 'relative', + whiteSpace: 'nowrap', + + ['::before']: { + content: '""', + position: 'absolute', + width: 0, + height: 0, + bottom: '100%', + left: '50%', + transform: 'translateX(-50%)', + + borderStyle: 'solid', + borderWidth: '0px 4px 6px 4px', + borderColor: `transparent transparent ${theme.colors.sub_white} transparent `, + }, +}); + +const HumanZipyoHowToContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '8px', + padding: '16px 12px', + margin: '0px 16px', + background: theme.colors.sub_white, + borderRadius: '8px', + alignItems: 'flex-start', + + ['>div']: { + display: 'flex', + flexDirection: 'column', + gap: '4px', + + ['>p']: { + margin: '0px', + + ['&.title']: { + ...theme.font.detail12Semibold, + color: theme.colors.sub_gray11, + }, + + ['&.description']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray10, + }, + }, + }, + + ['>button']: { + background: theme.colors.sub_blue5, + padding: '6px 10px', + borderRadius: '4px', + border: 'none', + + ...theme.font.detail12Semibold, + color: theme.colors.sub_gray1, + }, +}); + +const HumanZipyoSubText = styled.p({ + ...theme.font.detail12Semibold, + color: theme.colors.sub_blue7, + + margin: '0 16px', + width: '100%', + boxSizing: 'border-box', + whiteSpace: 'nowrap', +}); + +export { + HumanZipyoDescription, + HumanZipyoGuageChart, + HumanZipyoScoreText, + HumanZipyoScoreRange, + HumanZipyoHowToContainer, + HumanZipyoSubText, +}; diff --git a/src/components/Modal/CenterTutorial/AboutHumanZipyo/AboutHumanZipyo.tsx b/src/components/Modal/CenterTutorial/AboutHumanZipyo/AboutHumanZipyo.tsx new file mode 100644 index 00000000..8f14defc --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutHumanZipyo/AboutHumanZipyo.tsx @@ -0,0 +1,74 @@ +import useRouter from '@router/useRouter'; +import GuageChart from '@components/Search/GuageChart/GuageChart'; +import LogoSVG from '@assets/logo_blue.svg?react'; +import { ModalContainer, ModalContent, ModalTitleContainer } from '../CenterTutotial.Style'; +import { + HumanZipyoDescription, + HumanZipyoGuageChart, + HumanZipyoHowToContainer, + HumanZipyoScoreRange, + HumanZipyoScoreText, + HumanZipyoSubText, +} from './AboutHumanZipyo.Style'; + +const scoreText = ['대곰탕', '곰탕', '어?', '호황', '대호황']; +const scoreRange = [ + [0, 30], + [30, 40], + [40, 50], + [50, 70], + [70, 100], +]; + +const AboutHumanZipyo = () => { + const { navToAbout } = useRouter(); + + return ( + + + +

    점수란?

    +
    + + + 인간지표는 개미들의 ‘민심 온도계’예요. + 점수는 총 5단계로, 높을수록 시장 분위기가 들떠 있거나 + 과열된 상태를 뜻해요. + + +
    + +
    +
    + {scoreText.map((e, index) => ( + + LV{index + 1}. {e} + + ))} +
    +
    + {scoreRange.map((e, index) => { + return ( + + {e[0]}~{e[1]}점 + + ); + })} +
    +
    + +
    +

    점수는 어떻게 산출되나요?

    +

    + 대규모 감정분석 모델을 통해 각종 커뮤니티에서 투자자들 반응을 긍/부정으로 파악하여 점수를 산출해요 +

    +
    + +
    + *공식 지표가 아니므로 참고 용도로 활용해 주세요 +
    +
    + ); +}; + +export default AboutHumanZipyo; diff --git a/src/components/Modal/CenterTutorial/AboutHumanZipyo/useAboutHumanZipyo.tsx b/src/components/Modal/CenterTutorial/AboutHumanZipyo/useAboutHumanZipyo.tsx new file mode 100644 index 00000000..16dc8a1e --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutHumanZipyo/useAboutHumanZipyo.tsx @@ -0,0 +1,20 @@ +import useModal from '@components/Modal/useModal'; +import CenterTutorialLayout from '../Layout'; +import AboutHumanZipyo from './AboutHumanZipyo'; + +const useAboutHumanZipyo = (): { + openModal: () => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: CenterTutorialLayout, + Component: AboutHumanZipyo, + modalKey: 'aboutHumanZipyo', + showDelay: 100, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useAboutHumanZipyo; diff --git a/src/components/Modal/CenterTutorial/AboutKeyword/AboutKeyword.tsx b/src/components/Modal/CenterTutorial/AboutKeyword/AboutKeyword.tsx new file mode 100644 index 00000000..906dd17b --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutKeyword/AboutKeyword.tsx @@ -0,0 +1,21 @@ +import { ModalContainer, ModalContent, ModalDescriptionContainer, ModalTitleContainer } from '../CenterTutotial.Style'; + +const AboutKeyword = () => { + return ( + + +

    현재 가장 많이 언급되고 있는 키워드란?

    +
    + + +

    + 인간지표는 다양한 커뮤니티에서 수집한 데이터 기반의 워드클라우드(개미들의 목소리)를 제공합니다. + 현재 개미들이 가장 많이 언급하는 단어를 한눈에 확인할 수 있습니다. +

    +
    +
    +
    + ); +}; + +export default AboutKeyword; diff --git a/src/components/Modal/CenterTutorial/AboutKeyword/useAboutKeyword.tsx b/src/components/Modal/CenterTutorial/AboutKeyword/useAboutKeyword.tsx new file mode 100644 index 00000000..8af9e131 --- /dev/null +++ b/src/components/Modal/CenterTutorial/AboutKeyword/useAboutKeyword.tsx @@ -0,0 +1,20 @@ +import useModal from '@components/Modal/useModal'; +import CenterTutorialLayout from '../Layout'; +import AboutKeyword from './AboutKeyword'; + +const useAboutKeyword = (): { + openModal: () => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: CenterTutorialLayout, + Component: AboutKeyword, + modalKey: 'aboutKeyword', + showDelay: 100, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useAboutKeyword; diff --git a/src/components/Modal/CenterTutorial/CenterTutotial.Style.ts b/src/components/Modal/CenterTutorial/CenterTutotial.Style.ts new file mode 100644 index 00000000..5366c999 --- /dev/null +++ b/src/components/Modal/CenterTutorial/CenterTutotial.Style.ts @@ -0,0 +1,87 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const ModalLayout = styled.div( + ({ isShowModal, showDelay }: { isShowModal: boolean; showDelay: number }) => ({ + background: isShowModal ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0)', + backdropFilter: isShowModal ? 'blur(2px)' : '', + transition: `all ${showDelay}ms ease-in-out`, + + ['>div']: { opacity: isShowModal ? 1 : 0, transition: `all ${showDelay}ms ease-in-out` }, + }), + { + position: 'fixed', + width: '100%', + height: '100%', + top: 0, + left: '50%', + transform: 'translateX(-50%)', + right: 0, + bottom: 0, + zIndex: '100', + maxWidth: '1280px', + + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + padding: '30px', + boxSizing: 'border-box', + + ['>div']: { + background: theme.colors.sub_gray4, + width: '100%', + display: 'flex', + flexDirection: 'column', + borderRadius: '8px', + overflow: 'hidden', + }, + }, +); + +const ModalContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '10px', + overflow: 'hidden auto', + maxHeight: '100%', + height: '100%', + padding: '20px 0', + overscrollBehavior: 'contain', +}); + +const ModalTitleContainer = styled.div({ + display: 'flex', + padding: '0 16px', + gap: '4px', + + ['>svg']: { width: '72px', height: 'auto' }, + + ['>p']: { margin: '0', ...theme.font.body18Semibold, color: theme.colors.sub_black }, +}); + +const ModalContent = styled.div({ display: 'flex', flexDirection: 'column', gap: '10px' }); + +const ModalDescriptionContainer = styled.div({ + margin: '0 16px', + display: 'flex', + flexDirection: 'column', + + ['>p']: { + ...theme.font.body14Semibold, + color: theme.colors.sub_gray10, + margin: '0', + wordBreak: 'keep-all', + ['>b']: { ...theme.font.body14Bold, color: theme.colors.sub_blue6 }, + }, +}); + +const ModalCloseButton = styled.button({ + ...theme.font.body18Semibold, + color: theme.colors.sub_gray3, + background: theme.colors.sub_blue6, + border: 'none', + padding: '10px 0px', + width: '100%', +}); + +export { ModalLayout, ModalContainer, ModalTitleContainer, ModalContent, ModalDescriptionContainer, ModalCloseButton }; diff --git a/src/components/Modal/CenterTutorial/Layout.tsx b/src/components/Modal/CenterTutorial/Layout.tsx new file mode 100644 index 00000000..899c3c3e --- /dev/null +++ b/src/components/Modal/CenterTutorial/Layout.tsx @@ -0,0 +1,22 @@ +import { ModalLayoutProps } from '@components/Modal/useModal'; +import { ModalCloseButton, ModalLayout } from './CenterTutotial.Style'; + +const CenterTutorialLayout = ({ + children, + isShowModal, + modalRef, + handleClickOutSide, + closeModal, + showDelay, +}: ModalLayoutProps) => { + return ( + +
    + {children} + 이해했어요 +
    +
    + ); +}; + +export default CenterTutorialLayout; diff --git a/src/components/Modal/Confirm/ConfirmModal.Style.ts b/src/components/Modal/Confirm/ConfirmModal.Style.ts new file mode 100644 index 00000000..ce20bd59 --- /dev/null +++ b/src/components/Modal/Confirm/ConfirmModal.Style.ts @@ -0,0 +1,78 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const ConfirmModalLayout = styled.div({ + display: 'flex', + position: 'fixed', + background: 'rgba(0, 0, 0, 0.7)', + width: '100%', + height: '100%', + zIndex: '100', + top: '0', + left: '50%', + transform: 'translateX(-50%)', + alignItems: 'center', + justifyContent: 'center', + padding: '32px', + boxSizing: 'border-box', + backdropFilter: 'blur(5px)', + maxWidth: '1280px', +}); + +const ConfirmModalContainer = styled.div({ + background: 'white', + display: 'flex', + flexDirection: 'column', + width: '100%', + borderRadius: '20px', + padding: '24px 20px 16px', + gap: '28px', + + ['>div']: { + display: 'flex', + gap: '12px', + + ['>button']: { + ...theme.font.body18Semibold, + width: '100%', + height: '48px', + padding: '8px 0', + border: 'none', + borderRadius: '999px', + + [':first-of-type']: { + background: '#E9ECEF', + color: '#495057', + }, + [':last-of-type']: { + background: '#1B1C1E', + color: '#E9ECEF', + }, + }, + }, +}); + +const ConfirmModalTextContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '8px', + + ['>p']: { + margin: '0', + wordBreak: 'keep-all', + + ['&.title']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_gray8, + }, + + ['&.desc']: { + ...theme.font.body16Medium, + color: theme.colors.sub_gray7, + whiteSpace: 'pre-wrap', + wordBreak: 'keep-all', + }, + }, +}); + +export { ConfirmModalLayout, ConfirmModalContainer, ConfirmModalTextContainer }; diff --git a/src/components/Modal/Confirm/ConfirmModal.tsx b/src/components/Modal/Confirm/ConfirmModal.tsx new file mode 100644 index 00000000..6236501a --- /dev/null +++ b/src/components/Modal/Confirm/ConfirmModal.tsx @@ -0,0 +1,64 @@ +import { useEffect, useRef, useState } from 'react'; +import { ConfirmModalContainer, ConfirmModalLayout, ConfirmModalTextContainer } from './ConfirmModal.Style'; + +const ConfirmModal = ({ + title, + description, + onConfirm, + isInverse, + actionText = ['네', '아니오'], +}: { + title: string; + description?: string | React.ReactNode; + onConfirm: () => void; + isInverse?: boolean; + actionText?: string[]; +}): [() => React.ReactElement, () => void, () => void] => { + const [isOpen, setIsOpen] = useState(false); + + const modalRef = useRef(null); + + const openModal = () => { + setIsOpen(true); + }; + const closeModal = () => { + setIsOpen(false); + }; + + useEffect(() => { + const handleClick = (e: MouseEvent) => { + if (modalRef.current && !modalRef.current.contains(e.target as Node)) { + closeModal(); + } + }; + + window.addEventListener('mousedown', handleClick); + + return () => { + window.removeEventListener('mousedown', handleClick); + }; + }, []); + + const modal = () => { + if (!isOpen) return <>; + + return ( + + + +

    {title}

    + {description &&

    {description}

    } +
    +
    + + +
    +
    +
    + ); + }; + + return [modal, openModal, closeModal]; +}; + +export default ConfirmModal; diff --git a/src/components/Modal/ExperimentDetail/ExperimentDetail.Style.ts b/src/components/Modal/ExperimentDetail/ExperimentDetail.Style.ts new file mode 100644 index 00000000..cd54afaa --- /dev/null +++ b/src/components/Modal/ExperimentDetail/ExperimentDetail.Style.ts @@ -0,0 +1,290 @@ +import styled from '@emotion/styled'; +import { deltaToColor } from '@utils/ScoreConvert'; +import { theme } from '@styles/themes'; + +const ExperimentDetailContent = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '16px', + padding: '0 20px', +}); + +const RecortSheetTitleContainer = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '12px', + padding: '14px 10px', + borderBottom: `1px solid ${theme.colors.sub_gray10}`, + + ['>img']: { + width: '32px', + height: '32px', + borderRadius: '999px', + }, + + ['>p']: { + margin: '0', + ...theme.font.body18Semibold, + color: theme.colors.sub_gray1, + }, +}); + +const ExperimentDetailIndexListContainer = styled.div({ + display: 'flex', +}); + +const ExperimentDetailIndexItemContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + flexGrow: '1', + + ['p']: { + margin: '0', + }, + + ['>p.title']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + }, + + ['>div']: { + height: '52px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + + ['>p.value']: { + ...theme.font.body14Semibold, + color: theme.colors.sub_white, + }, + + ['>p.subValue']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray6, + + ['&.white']: { + color: theme.colors.sub_gray2, + }, + }, + + ['>span']: { + ...theme.font.body14Semibold, + }, + + ['&.roi']: { + background: theme.colors.sub_gray11, + border: `1px solid ${theme.colors.sub_gray9}`, + borderRadius: '4px', + width: '100%', + }, + }, +}); + +// Chart + +const ExperimentDetailChartContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '8px', + width: '100%', + padding: '8px 10px 22px', + background: theme.colors.sub_gray11, + boxSizing: 'border-box', +}); + +const ExperimentDetailChartGraphContainer = styled.div({ + position: 'relative', + flexGrow: 1, + height: '200px', + + ['>canvas']: { + width: '100%', + height: '100%', + }, +}); + +const ExperimentDetailChartLayer = styled.div({ + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + display: 'flex', + justifyContent: 'space-around', +}); + +const ExperimentDetailChartDot = styled.div( + ({ score, enabled, selected }: { score: number | null; enabled: boolean; selected: boolean }) => ({ + ['>span']: { + display: score != undefined ? 'block' : 'none', + bottom: `${score ? score : 0}%`, + scale: enabled ? '1' : '0', + background: selected ? theme.colors.sub_blue6 : theme.colors.sub_gray11, + boxShadow: selected ? `0 0 0px 3px ${theme.colors.sub_blue6}33, 0 0 0px 9px ${theme.colors.sub_blue6}33` : 'none', + }, + }), + { + position: 'relative', + width: '20px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + margin: '20px 0px', + + ['>span']: { + position: 'absolute', + width: '12px', + height: '12px', + border: `2px solid ${theme.colors.sub_blue6}`, + boxSizing: 'border-box', + borderRadius: '50%', + transform: 'translateY(50%)', + transition: 'all 0.2s ease-in-out', + }, + }, +); + +const ExperimentDetailChartInfoLine = styled.div( + ({ index, score }: { index: number; score: number }) => { + const direction = score > 50 ? 'bottom' : 'top'; + + return { + left: `${10 + index * 20}%`, + ['>span']: { + top: direction == 'bottom' ? `calc(${100 - score}% + 6px)` : '45%', + bottom: direction == 'top' ? `calc(${score}% + 6px)` : '40%', + + backgroundImage: `linear-gradient(to ${direction}, #FFFFFF80 0px, #2947D2 100%)`, + maskImage: `repeating-linear-gradient(to ${direction}, #FFFFFF, #FFFFFF 3px, transparent 3px, transparent 6px)`, + webkitMaskImage: `repeating-linear-gradient(to ${direction}, #FFFFFF, #FFFFFF 3px, transparent 3px, transparent 6px)`, + }, + }; + }, + { + position: 'absolute', + top: '0', + bottom: '0', + margin: '20px 0px', + // background: 'rgba(255, 255, 255, 1)', + + ['>span']: { + position: 'absolute', + width: '2px', + transform: 'translateX(-50%)', + }, + }, +); + +const ExperimentDetailChartInfoContent = styled.div( + ({ index, score }: { index: number; score: number }) => { + const left = index == 0 ? '0' : index == 1 ? '20%' : index == 2 ? '50%' : 'auto'; + const right = index == 4 ? '0' : index == 3 ? '20%' : 'auto'; + const transform = `translateX(${index == 4 ? '0%' : index == 3 ? '50%' : index == 2 ? '-50%' : index == 1 ? '-50%' : '0%'})`; + const margin = `24px ${[1, 3].includes(index) ? '10%' : '0px'} 24px`; + + const direction = score > 50 ? 'bottom' : 'top'; + + return { + left, + right, + transform, + margin, + bottom: direction == 'bottom' ? '0' : 'auto', + top: direction == 'top' ? `0` : 'auto', + }; + }, + { + background: 'rgba(255, 255, 255, 0.12)', + border: '1px solid rgba(255, 255, 255, 0.1)', + backdropFilter: 'blur(10px)', + borderRadius: '999px', + padding: '10px 20px', + boxSizing: 'border-box', + position: 'absolute', + + ['>div']: { + display: 'flex', + flexDirection: 'column', + }, + + ['>p']: { + margin: '0', + ...theme.font.detail10Medium, + color: theme.colors.sub_gray5, + }, + }, +); + +const ExperimentDetailChartInfoItemContainer = styled.div( + ({ delta }: { delta: number }) => ({ + ['>p.diff']: { + color: deltaToColor(delta) ?? theme.colors.sub_gray7, + }, + }), + { + display: 'flex', + alignItems: 'center', + gap: '4px', + + ['>p']: { + margin: '0', + whiteSpace: 'nowrap', + textAlign: 'left', + + ['&.name']: { + ...theme.font.detail10Bold, + color: theme.colors.sub_white, + width: '36px', + }, + ['&.value']: { + ...theme.font.detail10Medium, + color: theme.colors.sub_white, + }, + ['&.diff']: { + ...theme.font.detail10Medium, + }, + }, + + ['>span.divider']: { + width: '1px', + height: '10px', + background: theme.colors.sub_gray7, + }, + }, +); + +const ExperimentDetailChartDateContainer = styled.div({ + display: 'flex', + + ['>p']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + width: '100%', + textAlign: 'center', + margin: '0', + }, +}); + +// + +const ColoredDiffLabel = styled.span(({ delta }: { delta: number }) => ({ + color: deltaToColor(delta) ?? theme.colors.sub_gray7, +})); + +export { + ExperimentDetailContent, + RecortSheetTitleContainer, + ExperimentDetailIndexListContainer, + ExperimentDetailIndexItemContainer, + ExperimentDetailChartContainer, + ExperimentDetailChartGraphContainer, + ExperimentDetailChartLayer, + ExperimentDetailChartDot, + ExperimentDetailChartInfoLine, + ExperimentDetailChartInfoContent, + ExperimentDetailChartInfoItemContainer, + ExperimentDetailChartDateContainer, + ColoredDiffLabel, +}; diff --git a/src/components/Modal/ExperimentDetail/ExperimentDetail.tsx b/src/components/Modal/ExperimentDetail/ExperimentDetail.tsx new file mode 100644 index 00000000..873f44fe --- /dev/null +++ b/src/components/Modal/ExperimentDetail/ExperimentDetail.tsx @@ -0,0 +1,327 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; +import { STOCK_COUNTRY_MAP } from '@ts/StockCountry'; +import { getFormattedDate } from '@utils/dateFormatter'; +import useRouter from '@router/useRouter'; +import StockImage from '@components/Common/StockImage'; +import { ExperimentDetailTradeInfo } from '@controllers/experiment/api'; +import { useExperimentDetailQuery } from '@controllers/experiment/query'; +import { theme } from '@styles/themes'; +import { + ColoredDiffLabel, + ExperimentDetailChartContainer, + ExperimentDetailChartDateContainer, + ExperimentDetailChartDot, + ExperimentDetailChartGraphContainer, + ExperimentDetailChartInfoContent, + ExperimentDetailChartInfoItemContainer, + ExperimentDetailChartInfoLine, + ExperimentDetailChartLayer, + ExperimentDetailContent, + ExperimentDetailIndexItemContainer, + ExperimentDetailIndexListContainer, + RecortSheetTitleContainer, +} from './ExperimentDetail.Style'; +import { ExperimentDetailModalData } from './useExperimentDetail'; + +// const DPR = window.devicePixelRatio; + +export interface ExperimentDetailIndex { + key: string; + title: string; + value: string; + subValue?: string; +} + +const ExperimentDetail = ({ modalData: { experimentId } }: { modalData: ExperimentDetailModalData }) => { + const { navToStock } = useRouter(); + const { data: experimentDetail, isLoading } = useExperimentDetailQuery(experimentId); + + const indexList: ExperimentDetailIndex[] = useMemo(() => { + if (!experimentDetail) return []; + + const { buyAt, status, buyScore, buyPrice, currentScore, currentPrice, roi, country } = experimentDetail; + const currency = STOCK_COUNTRY_MAP[country].currency; + return [ + { + key: 'buyDate', + title: '매수일/상태', + value: `${getFormattedDate(buyAt!)}`, + subValue: `${status == 'PROGRESS' ? '실험중' : '실험완료'}`, + }, + { + key: 'buyTime', + title: '매수시점', + value: `${buyScore}점`, + subValue: `${currency}${buyPrice?.toLocaleString()}`, + }, + { + key: 'currentTime', + title: '현재시점', + value: `${currentScore}점`, + subValue: `${currency}${currentPrice?.toLocaleString()}`, + }, + { + key: 'roi', + title: '수익률', + value: `${roi && roi > 0 ? '+' : ''}${(isNaN(roi!) ? 0 : roi!).toFixed(1)}%`, + subValue: `${roi}`, + }, + ]; + }, [experimentDetail]); + + const handleClickTitle = () => { + const { symbolName, country } = experimentDetail!; + + navToStock(symbolName, country); + }; + + if (isLoading) return
    Loading...
    ; + + if (!experimentDetail) return null; + + return ( + + + +

    {experimentDetail.symbolName}

    +
    + + {indexList.map((item) => ( + + ))} + + +
    + ); +}; + +const ExperimentDetailIndexItem = ({ indexItem }: { indexItem: ExperimentDetailIndex }) => { + const { key, title, value, subValue } = indexItem; + return ( + +

    {title}

    +
    + {key != 'roi' ? ( + <> +

    {value}

    +

    {subValue}

    + + ) : ( + {value} + )} +
    +
    + ); +}; + +const ExperimentDetailChart = ({ + tradeInfos, + buyScore, + buyPrice, +}: { + tradeInfos: ExperimentDetailTradeInfo[]; + buyScore: number; + buyPrice: number; +}) => { + const chartCanvasRef = useRef(null); + + const loadChartStatusRef = useRef({ + time: 0, + isStart: false, + }); + const loadChartFrameRef = useRef(0); + + const [enableDots, setEnableDots] = useState(Array.from({ length: 5 }, () => false)); + const [selectedDot, setSelectedDot] = useState(-1); + + const drawChart = (noAnimation: boolean = false) => { + const canvas = chartCanvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + const { width, height } = canvas.getBoundingClientRect(); + + const length = 5; + const fullTime = 200; + const delay = fullTime / (length - 1); + + const { time } = loadChartStatusRef.current; + const xScale = (length - 1) / length; + const yScale = 0.8; + + ctx.clearRect(0, 0, width, height); + ctx.strokeStyle = theme.colors.sub_blue6; + ctx.lineWidth = 2; + + const getScaledCoord = (x: number, y: number): [number, number] => { + return [x * xScale + ((1 - xScale) / 2) * width, y * yScale + ((1 - yScale) / 2) * height]; + }; + + tradeInfos.forEach((e, index, arr) => { + if (time < index * delay) return; + if (index == length - 1 || index == arr.length - 1) return; + + const indexTime = time - index * delay; + + const startX = index * (width / (length - 1)); + const startY = height - e.score * (height / 100); + + const endX = (index + 1) * (width / (length - 1)); + const endY = height - arr[index + 1].score * (height / 100); + const slope = (endY - startY) / (endX - startX); + const x = Math.min(indexTime * (width / fullTime), endX - startX); + const Y = slope * x; + + ctx.beginPath(); + ctx.moveTo(...getScaledCoord(startX, startY)); + ctx.lineTo(...getScaledCoord(startX + x, startY + Y)); + ctx.closePath(); + ctx.stroke(); + }); + + tradeInfos.forEach((_, index) => { + if (time < index * delay - delay / 2) return; + + setEnableDots((prev) => { + const newEnableDots = [...prev]; + newEnableDots[index] = true; + return newEnableDots; + }); + if (index == tradeInfos.length - 1) { + if (selectedDot == -1) { + setTimeout(() => setSelectedDot(index), 100); + } + } + }); + + if (loadChartStatusRef.current.time < fullTime + 10 && !noAnimation) { + loadChartStatusRef.current.time++; + loadChartFrameRef.current = requestAnimationFrame(() => drawChart()); + } + return; + }; + + const [canvasInfo, setCanvasInfo] = useState({ + width: 0, + height: 0, + }); + + useEffect(() => { + const canvas = chartCanvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + const { width, height } = canvas.getBoundingClientRect(); + setCanvasInfo({ width, height }); + + if (loadChartStatusRef.current.isStart) return; + loadChartStatusRef.current.isStart = true; + loadChartFrameRef.current = requestAnimationFrame(() => drawChart()); + }, [tradeInfos]); + + useEffect(() => { + const container = chartCanvasRef.current; + if (!container) return; + + const resizeObserver = new ResizeObserver((entries) => { + const entry = entries[0]; + if (!entry) return; + + const { width, height } = entry.contentRect; + setCanvasInfo({ width, height }); + }); + + resizeObserver.observe(container); + + return () => { + resizeObserver.disconnect(); + }; + }, []); + + useEffect(() => { + drawChart(true); + }, [canvasInfo]); + + const selectedTradeInfo = selectedDot != -1 ? tradeInfos[selectedDot] : null; + const infoItemText = useMemo(() => { + if (!selectedTradeInfo) return null; + + const { score: currentScore, price: currentPrice } = selectedTradeInfo; + const scoreDiff = currentScore - buyScore; + const scoreDiffSign = scoreDiff > 0 ? '+' : ''; + const roi = buyPrice !== 0 ? ((currentPrice - buyPrice) / buyPrice) * 100 : 0; + const roiDiff = roi; + const roiDiffSign = roiDiff > 0 ? '+' : ''; + + return selectedTradeInfo + ? [ + { + name: '인간지표', + value: `${currentScore}점`, + diff: `(${scoreDiffSign}${scoreDiff}점)`, + delta: scoreDiff, + }, + { + name: '수익률', + value: `${roiDiffSign}${roi.toFixed(1)}%`, + diff: `(${roiDiffSign}${roiDiff.toFixed(1)}%)`, + delta: roiDiff, + }, + ] + : null; + }, [selectedTradeInfo, buyScore, buyPrice]); + return ( + + + + + {Array.from({ length: 5 }).map((_, index) => ( + + setSelectedDot((prev) => (prev == index ? -1 : index))} /> + + ))} + {infoItemText && ( + <> + + + + +
    + {infoItemText.map((e) => ( + +

    {e.name}

    + +

    {e.value}

    +

    {e.diff}

    +
    + ))} +
    +

    *()는 매수시점 대비

    +
    + + )} +
    +
    + + {Array.from({ length: 5 }).map((_, index) => ( +

    D-{5 - index}

    + ))} +
    +
    + ); +}; + +export default ExperimentDetail; diff --git a/src/components/Modal/ExperimentDetail/useExperimentDetail.ts b/src/components/Modal/ExperimentDetail/useExperimentDetail.ts new file mode 100644 index 00000000..35adf5df --- /dev/null +++ b/src/components/Modal/ExperimentDetail/useExperimentDetail.ts @@ -0,0 +1,24 @@ +import BottomUpCancel from '../Layout/BottomUpCancel/BottomUpCancel'; +import useModal from '../useModal'; +import ExperimentDetail from './ExperimentDetail'; + +export interface ExperimentDetailModalData { + experimentId: number; +} + +const useExperimentDetailModal = (): { + openModal: (modalData: ExperimentDetailModalData) => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: BottomUpCancel, + Component: ExperimentDetail, + modalKey: 'experimentDetail', + showDelay: 200, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useExperimentDetailModal; diff --git a/src/components/Modal/Layout/BottomUpCancel/BottomUpCancel.Style.ts b/src/components/Modal/Layout/BottomUpCancel/BottomUpCancel.Style.ts new file mode 100644 index 00000000..84e68ed7 --- /dev/null +++ b/src/components/Modal/Layout/BottomUpCancel/BottomUpCancel.Style.ts @@ -0,0 +1,58 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const ModalLayout = styled.div( + ({ isShowModal, showDelay }: { isShowModal: boolean; showDelay: number }) => ({ + background: isShowModal ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0)', + backdropFilter: isShowModal ? 'blur(2px)' : '', + transition: `all ${showDelay}ms ease-in-out`, + + ['>div']: { + transform: `translateY(${isShowModal ? '0' : '100%'})`, + transition: `all ${showDelay}ms ease-in-out`, + }, + }), + { + position: 'fixed', + width: '100%', + height: '100%', + top: 0, + left: '50%', + transform: 'translateX(-50%)', + right: 0, + bottom: 0, + zIndex: '100', + overflow: 'auto', + maxWidth: '1280px', + + display: 'flex', + flexDirection: 'column', + justifyContent: 'flex-end', + + ['>div']: { + background: theme.colors.sub_black, + width: '100%', + border: `1px solid ${theme.colors.sub_gray10}`, + borderRadius: '16px 16px 0 0', + boxSizing: 'border-box', + padding: '30px 0px 60px', + gap: '72px', + display: 'flex', + flexDirection: 'column', + overflow: 'auto', + overscrollBehavior: 'contain', + }, + }, +); + +const ModalCancelButton = styled.button({ + ...theme.font.body18Semibold, + color: theme.colors.sub_white, + background: theme.colors.sub_blue6, + borderRadius: '8px', + border: 'none', + padding: '10px 0px', + margin: '0px 20px', +}); + +export { ModalLayout, ModalCancelButton }; diff --git a/src/components/Modal/Layout/BottomUpCancel/BottomUpCancel.tsx b/src/components/Modal/Layout/BottomUpCancel/BottomUpCancel.tsx new file mode 100644 index 00000000..4612786a --- /dev/null +++ b/src/components/Modal/Layout/BottomUpCancel/BottomUpCancel.tsx @@ -0,0 +1,22 @@ +import { ModalLayoutProps } from '@components/Modal/useModal'; +import { ModalCancelButton, ModalLayout } from './BottomUpCancel.Style'; + +const BottomUpCancel = ({ + children, + isShowModal, + modalRef, + handleClickOutSide, + closeModal, + showDelay, +}: ModalLayoutProps) => { + return ( + +
    + {children} + 닫기 +
    +
    + ); +}; + +export default BottomUpCancel; diff --git a/src/components/Modal/MockPurchase/MockPurchase.tsx b/src/components/Modal/MockPurchase/MockPurchase.tsx new file mode 100644 index 00000000..67e46626 --- /dev/null +++ b/src/components/Modal/MockPurchase/MockPurchase.tsx @@ -0,0 +1,101 @@ +import styled from '@emotion/styled'; +import { useNavigate } from 'react-router'; +import { webPath } from '@router/index'; +import { theme } from '@styles/themes'; +import LabResultPNG from '@assets/lab/lab-result.png'; + +const MockPurchaseContainer = styled.div({ + paddingTop: '32px', + display: 'flex', + flexDirection: 'column', + gap: '36px', + flexGrow: '1', + padding: '32px 20px', + + ['>img']: { + borderRadius: '8px', + }, +}); + +const MockPurchaseTextGroup = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '12px', + + ['>p']: { + margin: '0', + + ['&.title']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_white, + }, + + ['&.subtitle']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + }, + }, +}); + +const MockPurchaseButtonContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '12px', + marginTop: 'auto', + + ['>button']: { + ...theme.font.body16Medium, + color: theme.colors.sub_white, + + padding: '10px 0px', + borderRadius: '8px', + cursor: 'pointer', + border: 'none', + + ['&.primary']: { + background: theme.colors.sub_blue6, + }, + + ['&.secondary']: { + background: theme.colors.sub_gray7, + }, + }, +}); + +const MockPurchase = () => { + const navigate = useNavigate(); + + const handleClickPrimary = () => { + navigate(webPath.lab, { replace: true }); + }; + + const handleClickSecondary = () => { + navigate(-1); + }; + + return ( + + +

    + 모의 매수 성공! 🎉
    + 5영업일 뒤, 결과를 알려드릴께요! +

    +

    + 모의매수한 종목은 언제든,
    + 실험실 홈에서 언제든 변경할 수 있어요! +

    +
    + + + + + +
    + ); +}; + +export default MockPurchase; diff --git a/src/components/Modal/MockPurchase/MockPurchaseLayout.tsx b/src/components/Modal/MockPurchase/MockPurchaseLayout.tsx new file mode 100644 index 00000000..19aca59e --- /dev/null +++ b/src/components/Modal/MockPurchase/MockPurchaseLayout.tsx @@ -0,0 +1,62 @@ +import styled from '@emotion/styled'; +import Header from '@layout/Header/Header'; +import { theme } from '@styles/themes'; +import { ModalLayoutProps } from '../useModal'; + +const ModalLayout = styled.div( + ({ isShowModal, showDelay }: { isShowModal: boolean; showDelay: number }) => ({ + background: isShowModal ? 'rgba(0, 0, 0, 1)' : 'rgba(0, 0, 0, 0)', + backdropFilter: isShowModal ? 'blur(2px)' : '', + transition: `all ${showDelay}ms ease-in-out`, + + ['>div']: { + transform: `translateY(${isShowModal ? '0' : '100dvh'})`, + transition: `all ${showDelay}ms ease-in-out`, + }, + }), + { + position: 'fixed', + top: 0, + left: '50%', + transform: 'translateX(-50%)', + width: '100%', + height: 'calc(100% - 96px)', + zIndex: 10, + display: 'flex', + flexDirection: 'column', + overflow: 'hidden auto', + maxWidth: '1280px', + + ['>div']: { + background: theme.colors.sub_black, + width: '100%', + height: '100%', + borderRadius: '16px 16px 0 0', + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + overflow: 'auto', + overscrollBehavior: 'contain', + }, + }, +); + +const MockPurchaseLayout = ({ + children, + isShowModal, + modalRef, + handleClickOutSide, + showDelay, + closeModal, +}: ModalLayoutProps) => { + return ( + +
    +
    + {children} +
    +
    + ); +}; + +export default MockPurchaseLayout; diff --git a/src/components/Modal/MockPurchase/useMockPurchase.ts b/src/components/Modal/MockPurchase/useMockPurchase.ts new file mode 100644 index 00000000..40eb18b7 --- /dev/null +++ b/src/components/Modal/MockPurchase/useMockPurchase.ts @@ -0,0 +1,20 @@ +import useModal from '../useModal'; +import MockPurchase from './MockPurchase'; +import MockPurchaseLayout from './MockPurchaseLayout'; + +const useMockPurchase = (): { + openModal: () => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: MockPurchaseLayout, + Component: MockPurchase, + modalKey: 'mockPurchase', + showDelay: 200, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useMockPurchase; diff --git a/src/components/Modal/SearchBar/AutoComplete/AutoComplete.Style.ts b/src/components/Modal/SearchBar/AutoComplete/AutoComplete.Style.ts new file mode 100644 index 00000000..1f4387db --- /dev/null +++ b/src/components/Modal/SearchBar/AutoComplete/AutoComplete.Style.ts @@ -0,0 +1,27 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const AutoCompleteEmptyContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '6px', + alignItems: 'center', + padding: '88px 0px', + + ['>p']: { + margin: '0', + whiteSpace: 'nowrap', + + ['&.empty_title']: { + ...theme.font.body18Medium, + color: theme.colors.sub_gray7, + }, + + ['&.empty_subtitle']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + }, + }, +}); + +export { AutoCompleteEmptyContainer }; diff --git a/src/components/Modal/SearchBar/AutoComplete/Keywords/Keywords.tsx b/src/components/Modal/SearchBar/AutoComplete/Keywords/Keywords.tsx new file mode 100644 index 00000000..3b0efbcc --- /dev/null +++ b/src/components/Modal/SearchBar/AutoComplete/Keywords/Keywords.tsx @@ -0,0 +1,69 @@ +import { useMemo } from 'react'; +import { useEffect } from 'react'; +import { SmallStockCard } from '@components/CardList/StockCard/StockCard'; +import { fetchSearchKeyword } from '@controllers/stocks/api'; +import { useAutoComplete } from '@controllers/stocks/query'; +import { SearchBarItemContainer, SearchBarItemContents, SearchBarItemTitle } from '../../SearchBar.Style'; +import { AutoCompleteEmptyContainer } from '../AutoComplete.Style'; + +const AutoCompleteKeywords = ({ searchValue }: { searchValue: string }) => { + const [searchedKeywords, setSearchedKeywords] = useAutoComplete(fetchSearchKeyword, 'keyword'); + + useEffect(() => { + setSearchedKeywords(searchValue); + }, [searchValue]); + + const stocksForRender = useMemo(() => { + if (!searchedKeywords?.length) return []; + + const keyword = searchedKeywords[0].keyword; + + return searchedKeywords.map(({ stockId, symbolName, score, diff, keywordNames, country }) => { + const safeKeywordNames = keywordNames ?? []; + const baseKeyword = safeKeywordNames[0]; // 없을 수도 있음 + + const keywords = safeKeywordNames.includes(keyword) + ? safeKeywordNames + : ([baseKeyword, keyword].filter(Boolean) as string[]); + + return { + stockId, + symbolName, + score, + diff, + keywords, + country, + }; + }); + }, [searchedKeywords]); + + const hasResults = stocksForRender.length > 0; + const realKeyword = searchedKeywords?.[0]?.keyword; + + return ( + + + 검색결과 + {hasResults && ( +

    + '{realKeyword}'이(가) 가장 많이 언급된 종목순으로 노출됩니다 +

    + )} +
    + + {hasResults ? ( + stocksForRender.map((stock) => ( + + )) + ) : ( + +

    '{searchValue}' 검색어에 해당하는 결과가 없어요 😭

    +

    다른 종목을 다시 검색해보세요

    +
    + )} +
    +
    + ); +}; + +export default AutoCompleteKeywords; diff --git a/src/components/Modal/SearchBar/AutoComplete/Stocks/Stocks.Style.ts b/src/components/Modal/SearchBar/AutoComplete/Stocks/Stocks.Style.ts new file mode 100644 index 00000000..a51d9319 --- /dev/null +++ b/src/components/Modal/SearchBar/AutoComplete/Stocks/Stocks.Style.ts @@ -0,0 +1,46 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const AutoCompleteStocksItem = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '12px', + overflow: 'hidden', + width: '100%', + + ['>p']: { + margin: '0', + + ['&.country']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + flexShrink: '0', + }, + + ['&.name']: { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + ...theme.font.body16Semibold, + color: theme.colors.sub_gray1, + + ['>b']: { + ...theme.font.body16Semibold, + color: theme.colors.sub_blue5, + }, + + ['>span']: { + padding: '0 4px', + ...theme.font.detail12Semibold, + color: theme.colors.sub_gray6, + + ['>b']: { + ...theme.font.detail12Semibold, + color: theme.colors.sub_blue5, + }, + }, + }, + }, +}); + +export { AutoCompleteStocksItem }; diff --git a/src/components/Modal/SearchBar/AutoComplete/Stocks/Stocks.tsx b/src/components/Modal/SearchBar/AutoComplete/Stocks/Stocks.tsx new file mode 100644 index 00000000..9a47f9ab --- /dev/null +++ b/src/components/Modal/SearchBar/AutoComplete/Stocks/Stocks.tsx @@ -0,0 +1,96 @@ +import { Fragment, useCallback, useEffect, useMemo } from 'react'; +import { StockCountryKey } from '@ts/StockCountry'; +import { STOCK_COUNTRY_MAP } from '@ts/StockCountry'; +import extractMatchedSegments from '@utils/extractMatchedSegments'; +import useRecentStocks from '@hooks/useRecentStocks'; +import useRouter from '@router/useRouter'; +import { fetchAutoComplete } from '@controllers/stocks/api'; +import { useAutoComplete } from '@controllers/stocks/query'; +import { SearchBarItemContainer, SearchBarItemContents, SearchBarItemTitle } from '../../SearchBar.Style'; +import { AutoCompleteEmptyContainer } from '../AutoComplete.Style'; +import { AutoCompleteStocksItem } from './Stocks.Style'; + +const AutoCompleteStocks = ({ searchValue }: { searchValue: string }) => { + const { navToStock } = useRouter(); + const [searchedStocks, setSearchedStocks] = useAutoComplete(fetchAutoComplete, 'symbolName'); + const { addRecentStock } = useRecentStocks(); + + useEffect(() => { + setSearchedStocks(searchValue); + }, [searchValue]); + + const handleStockClick = useCallback( + (e: React.MouseEvent) => { + const el = e.currentTarget as HTMLElement; + const symbolName = el.dataset.symbolname as string; + const country = el.dataset.country as StockCountryKey; + + addRecentStock(symbolName, country); + navToStock(symbolName, country, { replace: true }); + }, + [addRecentStock, navToStock], + ); + + const autoCompleteStocks = useMemo(() => { + if (!searchedStocks?.length) return []; + + return searchedStocks.map(({ stockId, symbolName, country, symbol }) => { + const nameSegments = extractMatchedSegments(symbolName, searchValue); + const symbolSegments = extractMatchedSegments(symbol, searchValue); + + return { + stockId, + symbolName, + country, + symbol, + nameSegments, + symbolSegments, + }; + }); + }, [searchedStocks, searchValue]); + + const hasResults = autoCompleteStocks.length; + + return ( + + 검색결과 + + {hasResults ? ( + autoCompleteStocks.map(({ stockId, symbolName, country, nameSegments, symbolSegments }) => ( + +

    {STOCK_COUNTRY_MAP[country].text}종목

    + +

    + {nameSegments.map(({ matched, text }, index) => { + const key = `SEARCHED_STOCK_${stockId}_NAME_${index}`; + + return matched ? {text} : {text}; + })} + + + {symbolSegments.map(({ matched, text }, index) => { + const key = `SEARCHED_STOCK_${stockId}_SYMBOL_${index}`; + + return matched ? {text} : {text}; + })} + +

    +
    + )) + ) : ( + +

    '{searchValue}' 검색어에 해당하는 결과가 없어요 😭

    +

    다른 종목을 다시 검색해보세요

    +
    + )} +
    +
    + ); +}; + +export default AutoCompleteStocks; diff --git a/src/components/Modal/SearchBar/PopularKeywords/PopularKeywords.Style.ts b/src/components/Modal/SearchBar/PopularKeywords/PopularKeywords.Style.ts new file mode 100644 index 00000000..78329d7d --- /dev/null +++ b/src/components/Modal/SearchBar/PopularKeywords/PopularKeywords.Style.ts @@ -0,0 +1,40 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const Wrapper = styled.div({ + flexGrow: 1, + display: 'flex', + flexDirection: 'column', + gap: '20px', + + ['>p']: { + ...theme.font.body18Semibold, + color: theme.colors.sub_white, + margin: '0 20px', + }, +}); + +const PopularKeywordsContainer = styled.div({ + display: 'flex', + padding: '0 20px', + overflow: 'auto', + gap: '8px', + + msOverflowStyle: 'none', + ['::-webkit-scrollbar']: { + display: 'none', + }, + + ['>span']: { + whiteSpace: 'nowrap', + overflow: 'hidden', + flexShrink: '0', + padding: '8px 16px', + borderRadius: '999px', + ...theme.font.body16Medium, + color: theme.colors.sub_gray1, + border: `1px solid ${theme.colors.sub_gray9}`, + }, +}); + +export { Wrapper, PopularKeywordsContainer }; diff --git a/src/components/Modal/SearchBar/PopularKeywords/PopularKeywords.tsx b/src/components/Modal/SearchBar/PopularKeywords/PopularKeywords.tsx new file mode 100644 index 00000000..c8885289 --- /dev/null +++ b/src/components/Modal/SearchBar/PopularKeywords/PopularKeywords.tsx @@ -0,0 +1,25 @@ +import { useKeywordRankingsQuery } from '@controllers/stocks/query'; +import { PopularKeywordsContainer, Wrapper } from './PopularKeywords.Style'; + +const PopularKeywords = ({ setInputValue }: { setInputValue: (value: string) => void }) => { + const { data: keywordRankings } = useKeywordRankingsQuery(); + + const handlePopularKeywordClick = (keyword: string) => () => { + setInputValue(keyword); + }; + + return ( + +

    현재 가장 많이 언급되는 키워드

    + + {keywordRankings?.map((e) => ( + + {e} + + ))} + +
    + ); +}; + +export default PopularKeywords; diff --git a/src/components/Modal/SearchBar/PopularStocks/PopularStocks.Style.ts b/src/components/Modal/SearchBar/PopularStocks/PopularStocks.Style.ts new file mode 100644 index 00000000..4418ce5c --- /dev/null +++ b/src/components/Modal/SearchBar/PopularStocks/PopularStocks.Style.ts @@ -0,0 +1,51 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const PopularStocksItem = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '16px', + + ['>p']: { + margin: '0', + ...theme.font.body16Semibold, + color: theme.colors.sub_blue6, + }, +}); + +const PopularStocksItemContents = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '10px', + overflow: 'hidden', + flexGrow: 1, + + ['>img']: { + width: '32px', + height: '32px', + aspectRatio: '1 / 1', + borderRadius: '50%', + flexShrink: '0', + background: theme.colors.sub_gray11, + }, + + ['>p']: { + ...theme.font.body16Semibold, + color: theme.colors.sub_gray1, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + flexGrow: 1, + margin: '0', + }, + + ['>svg']: { + width: '24px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray7, + flexShrink: '0', + }, +}); + +export { PopularStocksItem, PopularStocksItemContents }; diff --git a/src/components/Modal/SearchBar/PopularStocks/PopularStocks.tsx b/src/components/Modal/SearchBar/PopularStocks/PopularStocks.tsx new file mode 100644 index 00000000..34aabe59 --- /dev/null +++ b/src/components/Modal/SearchBar/PopularStocks/PopularStocks.tsx @@ -0,0 +1,42 @@ +import { StockCountryKey } from '@ts/StockCountry'; +import useRecentStocks from '@hooks/useRecentStocks'; +import useRouter from '@router/useRouter'; +import StockImage from '@components/Common/StockImage'; +import { usePopularStockFetchQuery } from '@controllers/stocks/query'; +import ChevronLeftSVG from '@assets/icons/chevronLeft.svg?react'; +import { SearchBarItemContainer, SearchBarItemContents, SearchBarItemTitle } from '../SearchBar.Style'; +import { PopularStocksItem, PopularStocksItemContents } from './PopularStocks.Style'; + +const PopularStocks = () => { + const { navToStock } = useRouter(); + const { addRecentStock } = useRecentStocks(); + const [popularStocks] = usePopularStockFetchQuery(); + + const handlePopularStockClick = (symbolName: string, country: StockCountryKey) => () => { + addRecentStock(symbolName, country); + navToStock(symbolName, country, { replace: true }); + }; + + return ( + + 인간지표 인기검색어 + + {popularStocks.map(({ stockId, symbolName, country }, index) => ( + +

    {index + 1}

    + + +

    {symbolName}

    + +
    +
    + ))} +
    +
    + ); +}; + +export default PopularStocks; diff --git a/src/components/Modal/SearchBar/RecentStocks/RecentStocks.Style.ts b/src/components/Modal/SearchBar/RecentStocks/RecentStocks.Style.ts new file mode 100644 index 00000000..04de8cdb --- /dev/null +++ b/src/components/Modal/SearchBar/RecentStocks/RecentStocks.Style.ts @@ -0,0 +1,53 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const RecentStocksItem = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '12px', + + ['>svg']: { + width: '24px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray9, + flexShrink: '0', + }, +}); + +const RecentStocksItemContents = styled.div({ + display: 'flex', + flexGrow: 1, + alignItems: 'center', + gap: '8px', + overflow: 'hidden', + + ['>p']: { + margin: '0', + + ['&.country']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + flexShrink: '0', + }, + + ['&.symbolName']: { + ...theme.font.body16Semibold, + color: theme.colors.sub_gray1, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + flexGrow: 1, + }, + }, + + ['>svg']: { + width: '24px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray9, + flexShrink: '0', + }, +}); + +export { RecentStocksItem, RecentStocksItemContents }; diff --git a/src/components/Modal/SearchBar/RecentStocks/RecentStocks.tsx b/src/components/Modal/SearchBar/RecentStocks/RecentStocks.tsx new file mode 100644 index 00000000..1000cb9d --- /dev/null +++ b/src/components/Modal/SearchBar/RecentStocks/RecentStocks.tsx @@ -0,0 +1,44 @@ +import { STOCK_COUNTRY_MAP, StockCountryKey } from '@ts/StockCountry'; +import useRecentStocks from '@hooks/useRecentStocks'; +import useRouter from '@router/useRouter'; +import ClockSVG from '@assets/icons/clock.svg?react'; +import CrossSVG from '@assets/icons/cross.svg?react'; +import { SearchBarItemContainer, SearchBarItemContents, SearchBarItemTitle } from '../SearchBar.Style'; +import { RecentStocksItem, RecentStocksItemContents } from './RecentStocks.Style'; + +const RecentStocks = () => { + const { navToStock } = useRouter(); + + const { recentStocks, addRecentStock, removeRecentStock } = useRecentStocks(); + + const handleRecentStockDelete = (symbolName: string) => (e: React.MouseEvent) => { + e.stopPropagation(); + + removeRecentStock(symbolName); + }; + + const handleRecentStockClick = (symbolName: string, country: StockCountryKey) => () => { + addRecentStock(symbolName, country); + navToStock(symbolName, country, { replace: true }); + }; + + return ( + + 최근 검색어 + + {(recentStocks ?? []).map(({ symbolName, country }) => ( + + + +

    {STOCK_COUNTRY_MAP[country].text}종목

    +

    {symbolName}

    + +
    +
    + ))} +
    +
    + ); +}; + +export default RecentStocks; diff --git a/src/components/Modal/SearchBar/SearchBar.Style.ts b/src/components/Modal/SearchBar/SearchBar.Style.ts new file mode 100644 index 00000000..9b4a71c5 --- /dev/null +++ b/src/components/Modal/SearchBar/SearchBar.Style.ts @@ -0,0 +1,161 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const SearchBarContainer = styled.div({ + padding: '20px 0', + flexGrow: 1, + display: 'flex', + flexDirection: 'column', + gap: '45px', +}); + +const SearchBarContents = styled.div({ + padding: '0 20px', + display: 'flex', + alignItems: 'center', + gap: '12px', +}); + +const SearchBarSelectBox = styled.div({ + position: 'relative', + flexShrink: '0', + + ['>label']: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: '10px', + borderRadius: '8px', + cursor: 'pointer', + border: `1.5px solid transparent`, + background: theme.colors.sub_gray11, + color: theme.colors.sub_gray2, + ...theme.font.body16Medium, + + ['>svg']: { + width: '24px', + height: 'auto', + aspectRatio: '1 / 1', + }, + + [':focus']: { + borderColor: theme.colors.sub_gray7, + ['>svg']: { + transform: 'rotate(180deg);', + }, + + ['+ul']: { + display: 'block', + borderColor: theme.colors.sub_gray7, + color: theme.colors.sub_gray2, + }, + }, + }, + + ['>ul']: { + display: 'none', + position: 'absolute', + top: '100%', + left: '0', + listStyle: 'none', + padding: '0', + margin: '8px 0 0', + width: '100%', + zIndex: '1', + border: `1.5px solid ${theme.colors.sub_gray11}`, + borderRadius: '8px', + background: theme.colors.sub_gray11, + + ['>li']: { + padding: '12px 14px', + ...theme.font.body16Medium, + cursor: 'pointer', + }, + }, +}); + +const SearchBarInput = styled.label({ + display: 'flex', + background: theme.colors.sub_gray11, + borderRadius: '6px', + padding: '10px 16px', + flexGrow: 1, + minWidth: '0', + + ['>input']: { + flexGrow: '1', + background: 'none', + border: 'none', + outline: 'none', + ...theme.font.body16Medium, + color: theme.colors.sub_gray2, + minWidth: '0', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + + ['::placeholder']: { + color: theme.colors.sub_gray8, + }, + + ['&:focus']: { + ['::placeholder']: { + color: 'transparent', + }, + }, + }, + + ['>svg']: { + width: '24px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray8, + flexShrink: '0', + }, +}); + +const SearchBarItemContainer = styled.div({ + padding: '0 20px', + display: 'flex', + flexDirection: 'column', + gap: '20px', +}); + +const SearchBarItemTitle = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '8px', + + ...theme.font.body18Semibold, + color: theme.colors.sub_white, + + ['>p']: { + margin: '0', + + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + + ['>b']: { + flexShrink: '0', + ...theme.font.body14Semibold, + color: theme.colors.sub_gray3, + marginRight: '4px', + }, + }, +}); + +const SearchBarItemContents = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '20px', +}); + +export { + SearchBarContainer, + SearchBarContents, + SearchBarSelectBox, + SearchBarInput, + SearchBarItemContainer, + SearchBarItemTitle, + SearchBarItemContents, +}; diff --git a/src/components/Modal/SearchBar/SearchBar.tsx b/src/components/Modal/SearchBar/SearchBar.tsx new file mode 100644 index 00000000..e5254708 --- /dev/null +++ b/src/components/Modal/SearchBar/SearchBar.tsx @@ -0,0 +1,94 @@ +import { useCallback, useEffect, useState } from 'react'; +import { SEARCH_CATEGORIES, SEARCH_CATEGORY_MAP, SearchCategoryKey } from '@ts/SearchCategory'; +import ChevronDownSVG from '@assets/icons/chevronDown.svg?react'; +import CrossSVG from '@assets/icons/cross.svg?react'; +import SearchSVG from '@assets/icons/search.svg?react'; +import AutoCompleteKeywords from './AutoComplete/Keywords/Keywords'; +import AutoCompleteStocks from './AutoComplete/Stocks/Stocks'; +import PopularKeywords from './PopularKeywords/PopularKeywords'; +import PopularStocks from './PopularStocks/PopularStocks'; +import RecentStocks from './RecentStocks/RecentStocks'; +import { SearchBarContainer, SearchBarContents, SearchBarInput, SearchBarSelectBox } from './SearchBar.Style'; +import { SearchBarModalData } from './useSearchBarModal'; + +const SearchBar = ({ + modalData: { type = 'STOCK', value = '' }, +}: { + modalData: SearchBarModalData; +}): JSX.Element | null => { + const [searchType, setSearchType] = useState(type); + const [inputValue, setInputValue] = useState(value); + + const selectedSearchType = SEARCH_CATEGORY_MAP[searchType].text; + const [searchValue, setSearchValue] = useState(''); + + const handleSearchTypeMouseDown = useCallback( + (key: SearchCategoryKey) => () => { + setSearchType(key); + }, + [], + ); + + const handleSearchValueChange = useCallback((e: React.ChangeEvent) => { + setInputValue(e.target.value); + }, []); + + const handleSearchValueClear = useCallback(() => { + setInputValue(''); + }, []); + + useEffect(() => { + const id = window.setInterval(() => { + const curr = inputValue.trim(); + + setSearchValue(curr); + }, 200); + + return () => window.clearInterval(id); + }, [inputValue]); + + return ( + + + + +
      + {SEARCH_CATEGORIES.map(({ key, text }) => ( +
    • + {text} +
    • + ))} +
    +
    + + + {searchValue ? : } + +
    + {searchValue ? ( + searchType === 'STOCK' ? ( + + ) : ( + + ) + ) : searchType === 'STOCK' ? ( + <> + + + + ) : ( + + )} +
    + ); +}; + +export default SearchBar; diff --git a/src/components/Modal/SearchBar/SearchBarLayout.tsx b/src/components/Modal/SearchBar/SearchBarLayout.tsx new file mode 100644 index 00000000..c1163da0 --- /dev/null +++ b/src/components/Modal/SearchBar/SearchBarLayout.tsx @@ -0,0 +1,63 @@ +import styled from '@emotion/styled'; +import Header from '@layout/Header/Header'; +import { theme } from '@styles/themes'; +import { ModalLayoutProps } from '../useModal'; + +const ModalLayout = styled.div( + ({ isShowModal, showDelay }: { isShowModal: boolean; showDelay: number }) => ({ + background: isShowModal ? 'rgba(0, 0, 0, 1)' : 'rgba(0, 0, 0, 0)', + backdropFilter: isShowModal ? 'blur(2px)' : '', + transition: `all ${showDelay}ms ease-in-out`, + + ['>div']: { + transform: `translateY(${isShowModal ? '0' : '100dvh'})`, + transition: `all ${showDelay}ms ease-in-out`, + }, + }), + { + position: 'fixed', + top: 0, + left: '50%', + transform: 'translateX(-50%)', + width: '100%', + height: '100%', + zIndex: 1000, + display: 'flex', + flexDirection: 'column', + overflow: 'hidden auto ', + maxWidth: '1280px', + + ['>div']: { + background: theme.colors.sub_black, + width: '100%', + height: '100%', + borderRadius: '16px 16px 0 0', + boxSizing: 'border-box', + padding: '0px 0px 60px', + display: 'flex', + flexDirection: 'column', + overflow: 'auto', + overscrollBehavior: 'contain', + }, + }, +); + +const SearchBarLayout = ({ + children, + isShowModal, + modalRef, + handleClickOutSide, + showDelay, + closeModal, +}: ModalLayoutProps) => { + return ( + +
    +
    + {children} +
    +
    + ); +}; + +export default SearchBarLayout; diff --git a/src/components/Modal/SearchBar/useSearchBarModal.ts b/src/components/Modal/SearchBar/useSearchBarModal.ts new file mode 100644 index 00000000..0d418024 --- /dev/null +++ b/src/components/Modal/SearchBar/useSearchBarModal.ts @@ -0,0 +1,26 @@ +import { SearchCategoryKey } from '@ts/SearchCategory'; +import useModal from '../useModal'; +import SearchBar from './SearchBar'; +import SearchBarLayout from './SearchBarLayout'; + +export interface SearchBarModalData { + type?: SearchCategoryKey; + value?: string; +} + +const useSearchBarModal = (): { + openModal: (modalData: SearchBarModalData) => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const { Modal, openModal, closeModal } = useModal({ + Layout: SearchBarLayout, + Component: SearchBar, + modalKey: 'searchBar', + showDelay: 150, + }); + + return { Modal, openModal, closeModal }; +}; + +export default useSearchBarModal; diff --git a/src/components/Modal/useModal.tsx b/src/components/Modal/useModal.tsx new file mode 100644 index 00000000..52bfd1e7 --- /dev/null +++ b/src/components/Modal/useModal.tsx @@ -0,0 +1,117 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +export interface ModalLayoutProps { + children?: React.ReactNode; + isShowModal: boolean; + modalRef: React.RefObject; + handleClickOutSide: (e: React.MouseEvent) => void; + closeModal: () => void; + showDelay: number; +} + +const useModal = ({ + Layout, + Component, + modalKey, + showDelay = 0, +}: { + Layout: ({ + children, + isShowModal, + modalRef, + handleClickOutSide, + closeModal, + showDelay, + }: ModalLayoutProps) => JSX.Element; + Component: ({ modalData }: { modalData: T }) => JSX.Element | null; + modalKey: string; + showDelay?: number; +}): { + openModal: (modalData: T) => void; + closeModal: () => void; + Modal: JSX.Element | null; +} => { + const location = useLocation(); + const navigate = useNavigate(); + + const { state = {} } = location; + + const [isShowModal, setIsShowModal] = useState(false); + const [isOpenModal, setIsOpenModal] = useState(false); + const [modalData, setModalData] = useState(); + + const modalRef = useRef(null); + const showModalTimeoutRef = useRef(null); + + const openModal = (modalData: T) => { + localStorage.setItem('scrollPosition', window.scrollY.toString()); + navigate(location, { + state: { + ...state, + [modalKey]: { + isOpen: true, + modalData: modalData, + }, + }, + }); + }; + + const closeModal = () => { + localStorage.setItem('scrollPosition', window.scrollY.toString()); + navigate(-1); + }; + + useEffect(() => { + const scrollPosition = localStorage.getItem('scrollPosition'); + if (scrollPosition) { + window.scrollTo(0, parseInt(scrollPosition)); + localStorage.removeItem('scrollPosition'); + } + + const { isOpen, modalData } = location?.state?.[modalKey] ?? {}; + if (isOpen) { + setIsOpenModal(true); + setModalData(modalData); + showModalTimeoutRef.current = setTimeout(() => { + setIsShowModal(true); + }, 0); + } else { + setIsShowModal(false); + showModalTimeoutRef.current = setTimeout(() => { + setIsOpenModal(false); + setModalData(undefined); + }, showDelay); + } + }, [location]); + + const handleClickOutSide = (e: React.MouseEvent) => { + if (modalRef.current && e.target == modalRef.current) { + closeModal(); + } + }; + + const Modal = useMemo(() => { + if (!isOpenModal) return null; + + return ( + + + + ); + }, [isShowModal, isOpenModal, modalData]); + + return { + Modal, + openModal, + closeModal, + }; +}; + +export default useModal; diff --git a/src/components/PWAUsage/Android/Android.style.tsx b/src/components/MyPage/MyPage.Style.ts similarity index 100% rename from src/components/PWAUsage/Android/Android.style.tsx rename to src/components/MyPage/MyPage.Style.ts diff --git a/src/components/MyPage/MyPageInput/MyPageInput.Style.ts b/src/components/MyPage/MyPageInput/MyPageInput.Style.ts new file mode 100644 index 00000000..bb193687 --- /dev/null +++ b/src/components/MyPage/MyPageInput/MyPageInput.Style.ts @@ -0,0 +1,67 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const MyPageInputContainer = styled.div( + ({ isError }: { isError: boolean }) => ({ + ['>p']: { + color: isError ? theme.colors.sub_red : theme.colors.sub_gray3, + }, + + ['>input']: { + outline: isError ? `1px solid ${theme.colors.sub_red}` : 'none', + }, + }), + { + display: 'flex', + flexDirection: 'column', + gap: '8px', + padding: '0 20px', + + ['>p']: { + margin: '0', + ...theme.font.body16Medium, + }, + + ['>input']: { + border: 'none', + padding: '20px 16px', + height: '48px', + boxSizing: 'border-box', + borderRadius: '5px', + background: theme.colors.sub_gray11, + color: theme.colors.sub_gray3, + ...theme.font.body16Medium, + + ['&::placeholder']: { + color: theme.colors.sub_gray8, + }, + }, + }, +); + +const MyPageInputSubContainer = styled.div({ + display: 'flex', + justifyContent: 'space-between', + height: '20px', + + ['>p']: { + margin: '0', + + ['&.error']: { + padding: '0 4px', + ...theme.font.body14Medium, + color: theme.colors.sub_red, + }, + + ['&.sub']: { + ...theme.font.body14Regular, + color: theme.colors.sub_gray3, + + ['>span']: { + ...theme.font.body14Semibold, + }, + }, + }, +}); + +export { MyPageInputContainer, MyPageInputSubContainer }; diff --git a/src/components/MyPage/MyPageInput/MyPageInput.tsx b/src/components/MyPage/MyPageInput/MyPageInput.tsx new file mode 100644 index 00000000..7f144bee --- /dev/null +++ b/src/components/MyPage/MyPageInput/MyPageInput.tsx @@ -0,0 +1,42 @@ +import { MyPageInputContainer, MyPageInputSubContainer } from './MyPageInput.Style'; + +export interface MyPageInputProps { + name: string; + error: string; + title: string; + sub?: React.ReactElement; + inputs: { + key: string; + value: string; + placeholder: string; + disabled?: boolean; + handleChange: (e: React.ChangeEvent) => void; + }[]; +} + +const MyPageInput = (props: MyPageInputProps) => { + const { name, error, title, sub, inputs } = props; + + return ( + +

    {title}

    + {inputs.map((e, i) => ( + + ))} + +

    {error}

    + {!!sub &&

    {sub}

    } +
    +
    + ); +}; + +export default MyPageInput; diff --git a/src/components/MyPage/ProfileCircle/ProfileCircle.Style.ts b/src/components/MyPage/ProfileCircle/ProfileCircle.Style.ts new file mode 100644 index 00000000..4e7d54b9 --- /dev/null +++ b/src/components/MyPage/ProfileCircle/ProfileCircle.Style.ts @@ -0,0 +1,42 @@ +import styled from '@emotion/styled'; + +const ProfileCircleContainer = styled.label( + ({ size }: { size: 'small' | 'medium' | 'large' }) => ({ + ['>img']: { + width: size === 'small' ? '48px' : size === 'medium' ? '64px' : '76px', + }, + + ['>svg']: { + width: size === 'small' ? '16px' : size === 'medium' ? '20px' : '24px', + }, + }), + { + display: 'flex', + position: 'relative', + + ['>img']: { + height: 'auto', + aspectRatio: '1 / 1', + objectFit: 'cover', + borderRadius: '999px', + flexShrink: '0', + }, + + ['>svg']: { + position: 'absolute', + bottom: '0', + right: '0', + height: 'auto', + aspectRatio: '1 / 1', + fill: '#ADB5BD', + background: '#495057', + borderRadius: '999px', + }, + + ['>input']: { + display: 'none', + }, + }, +); + +export { ProfileCircleContainer }; diff --git a/src/components/MyPage/ProfileCircle/ProfileCircle.tsx b/src/components/MyPage/ProfileCircle/ProfileCircle.tsx new file mode 100644 index 00000000..b302891c --- /dev/null +++ b/src/components/MyPage/ProfileCircle/ProfileCircle.tsx @@ -0,0 +1,31 @@ +import EditCircleSVG from '@assets/edit_circle.svg?react'; +import ProfilePNG from '@assets/profile.png'; +import { ProfileCircleContainer } from './ProfileCircle.Style'; + +const ProfileCircle = ({ + profileImage, + handleChangeFile, + size, + canEdit = true, + handleClickCircle, +}: { + profileImage: string; + handleChangeFile: (e: React.ChangeEvent) => void; + size: 'small' | 'medium' | 'large'; + canEdit?: boolean; + handleClickCircle?: (e: React.MouseEvent) => void; +}) => { + return ( + + + {canEdit && ( + <> + + + + )} + + ); +}; + +export default ProfileCircle; diff --git a/src/components/NoLoginWrapper/NoLoginWrapper.style.ts b/src/components/NoLoginWrapper/NoLoginWrapper.style.ts new file mode 100644 index 00000000..443063d9 --- /dev/null +++ b/src/components/NoLoginWrapper/NoLoginWrapper.style.ts @@ -0,0 +1,88 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +export const Overlay = styled.div( + ({ hasHeader, hasNavbar }: { hasHeader?: boolean; hasNavbar?: boolean }) => ({ + top: hasHeader ? '60px' : 0, + bottom: hasNavbar ? '96px' : 0, + }), + { + position: 'fixed', + left: '50%', + transform: 'translateX(-50%)', + right: 0, + zIndex: 50, + backdropFilter: 'blur(5px)', + WebkitBackdropFilter: 'blur(5px)', + gap: '20px', + maxWidth: '1280px', + width: '100%', + background: 'linear-gradient(180deg, rgba(16, 16, 16, 0.4) 0%, #101010 44.56%)', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + + ['>div']: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + overflow: 'auto', + overscrollBehavior: 'contain', + padding: '100px 20px', + }, + }, +); + +export const TitleContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + gap: '10px', + + ['>p']: { + margin: '0', + textAlign: 'center', + whiteSpace: 'pre-wrap', + + ['&.title']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_gray2, + }, + + ['&.description']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray4, + }, + }, +}); + +export const ButtonContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + gap: '20px', + width: '220px', + + ['>button']: { + ...theme.font.body18Semibold, + appearance: 'none', + border: 0, + padding: '10px 28px', + borderRadius: '999px', + boxSizing: 'border-box', + width: '100%', + + ['&.primary']: { + color: theme.colors.sub_gray11, + background: theme.colors.sub_white, + }, + + ['&.secondary']: { + color: theme.colors.sub_gray5, + background: theme.colors.sub_gray9, + }, + }, +}); diff --git a/src/components/NoLoginWrapper/NoLoginWrapper.tsx b/src/components/NoLoginWrapper/NoLoginWrapper.tsx new file mode 100644 index 00000000..301c0b03 --- /dev/null +++ b/src/components/NoLoginWrapper/NoLoginWrapper.tsx @@ -0,0 +1,74 @@ +import { ReactNode } from 'react'; +import { useNavigate } from 'react-router-dom'; +import useAuthInfo from '@hooks/useAuthInfo'; +import { ButtonContainer, Overlay, TitleContainer } from './NoLoginWrapper.style'; + +export interface NoLoginWrapperProps { + title: ReactNode; + description: string | ReactNode; + buttonText: string; + children?: ReactNode; + className?: string; + SecondaryButtonText?: string; + hasHeader?: boolean; + hasNavbar?: boolean; + returnState?: unknown; // 로그인 후 돌아올 때 복원할 state +} + +const NoLoginWrapper = (props: NoLoginWrapperProps) => { + const { isLogin, handleNavigateLogin } = useAuthInfo(); + const navigate = useNavigate(); + + const { + title, + description, + buttonText, + children, + className, + SecondaryButtonText, + hasHeader, + hasNavbar, + returnState, + } = props; + + const handleClick = () => { + handleNavigateLogin({ returnState }); + }; + + const handleSecondaryClick = () => { + navigate('/'); + }; + + if (isLogin) return null; + + return ( + +
    + +

    {title}

    +

    {description}

    +
    + {children} + + + {SecondaryButtonText && ( + + )} + +
    +
    + ); +}; + +export default NoLoginWrapper; diff --git a/src/components/PWAUsage/Android/Android.tsx b/src/components/PWAUsage/Android/Android.tsx deleted file mode 100644 index 6fc407fa..00000000 --- a/src/components/PWAUsage/Android/Android.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import AddToHomeSVG from '@assets/PWA/Android/AddToHome.svg?react'; -import ShareButtonSVG from '@assets/PWA/Android/ShareButton.svg?react'; -import RunAppSVG from '@assets/PWA/RunApp.svg?react'; -import { - DetailContainer, - DetailItem, - DetailNumber, - DetailText, - HeaderText, - OrderContainer, -} from '../Common.style'; - -const IOS = () => ( - - - 홈화면에 앱을
    - 추가하세요. -
    - - -
    - 1 - chrome 접속, 상단 우측 버튼 탭 -
    - -
    - -
    - 2 - 홈 화면에 추가 -
    - -
    - -
    - 3 - 생성된 앱 실행 -
    - -
    -
    -
    -); - -export default IOS; diff --git a/src/components/PWAUsage/Common.style.ts b/src/components/PWAUsage/Common.style.ts deleted file mode 100644 index 90544e3d..00000000 --- a/src/components/PWAUsage/Common.style.ts +++ /dev/null @@ -1,76 +0,0 @@ -import styled from '@emotion/styled'; -import { theme } from '@styles/themes'; - -const OrderContainer = styled.div({ - boxSizing: 'border-box', - display: 'flex', - width: '100%', - - flexDirection: 'column', - alignItems: 'flex-start', - gap: '20px', - marginTop: '30px', -}); - -const HeaderText = styled.h2({ - color: theme.colors.primary0, - fontFamily: 'Pretendard', - fontSize: '25px', - fontStyle: 'normal', - fontWeight: 700, - lineHeight: '1.5', - textAlign: 'left', -}); - -const DetailContainer = styled.div({ - display: 'flex', - flexDirection: 'column', // 세로 정렬로 변경 - gap: '50px', // 각 항목 간 간격 - width: '100%', // 전체 너비 차지 -}); - -const DetailItem = styled.div({ - display: 'flex', - width: '100%', - flexDirection: 'column', - alignItems: 'flex-start', - gap: '22px', - - ['div']: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - gap: '12px', - }, - - ['svg']: { - alignItems: 'flex-start', - maxWidth: '100%', - }, -}); - -const DetailNumber = styled.div({ - width: '20px', - height: '20px', - fontSize: '16px', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - fontWeight: 700, - borderRadius: '4px', - background: theme.colors.primary50, - color: theme.colors.primary0, -}); - -const DetailText = styled.div({ - color: theme.colors.primary0, - fontFamily: 'Pretendard', - fontSize: '20px', - fontStyle: 'normal', - fontWeight: 700, - lineHeight: '1.5', - textAlign: 'left', - flex: 1, // 텍스트 영역 확장 -}); - -export { OrderContainer, HeaderText, DetailContainer, DetailItem, DetailNumber, DetailText }; diff --git a/src/components/PWAUsage/iOS/IOS.style.ts b/src/components/PWAUsage/iOS/IOS.style.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/PWAUsage/iOS/IOS.tsx b/src/components/PWAUsage/iOS/IOS.tsx deleted file mode 100644 index b16afec6..00000000 --- a/src/components/PWAUsage/iOS/IOS.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import AddToHomeSVG from '@assets/PWA/IOS/AddToHome.svg?react'; -import ShareButtonSVG from '@assets/PWA/IOS/ShareButton.svg?react'; -import RunAppSVG from '@assets/PWA/RunApp.svg?react'; -import { - DetailContainer, - DetailItem, - DetailNumber, - DetailText, - HeaderText, - OrderContainer, -} from '../Common.style'; - -const IOS = () => ( - - - 홈화면에 앱을
    - 추가하세요. -
    - - -
    - 1 - safari 접속, 하단 공유 버튼 탭 -
    - -
    - -
    - 2 - 홈 화면에 추가 -
    - -
    - -
    - 3 - 생성된 앱 실행 -
    - -
    -
    -
    -); - -export default IOS; diff --git a/src/components/Page/Home/Banner/Banner.Style.ts b/src/components/Page/Home/Banner/Banner.Style.ts new file mode 100644 index 00000000..a02ce4b2 --- /dev/null +++ b/src/components/Page/Home/Banner/Banner.Style.ts @@ -0,0 +1,122 @@ +// ad +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const BannerContainer = styled.div({ + display: 'flex', + overflow: 'auto', + width: '100%', + height: '240px', + scrollSnapType: 'x mandatory', + + msOverflowStyle: 'none', + ['::-webkit-scrollbar']: { + display: 'none', + }, +}); + +const BannerItemContainer = styled.div( + ({ backgroundColor }: { backgroundColor: string }) => ({ + background: backgroundColor, + }), + { + position: 'relative', + flexShrink: '0', + display: 'flex', + flexDirection: 'column', + width: '100%', + height: '100%', + padding: '24px 24px 18px', + boxSizing: 'border-box', + justifyContent: 'space-between', + scrollSnapAlign: 'start', + }, +); + +const BannerItemContent = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '12px', + alignItems: 'flex-start', +}); + +const BannerItemTextGroup = styled.div({ + display: 'flex', + flexDirection: 'column', + + ['>p']: { + margin: '0', + + ['&.title']: { + ...theme.font.heading24Semibold, + color: theme.colors.sub_white, + whiteSpace: 'pre', + }, + ['&.sub']: { + ...theme.font.title20Medium, + color: theme.colors.sub_white, + }, + }, +}); + +const BannerItemButton = styled.button({ + ...theme.font.body14Medium, + color: theme.colors.sub_white, + borderRadius: '999px', + padding: '6px 12px', + background: `${theme.colors.sub_white}1f`, + border: `1px solid ${theme.colors.sub_white}1a`, + backdropFilter: 'blur(10px)', +}); + +const BannerItemIndex = styled.p({ + ...theme.font.body16Medium, + color: theme.colors.sub_gray5, + margin: '0px', + + ['>b']: { + ...theme.font.body16Semibold, + color: theme.colors.sub_white, + }, +}); + +const BannerItemDecoration = styled.span({ + position: 'absolute', + + [':nth-of-type(1)']: { + bottom: '0px', + left: '0px', + width: '100%', + height: '57px', + background: `rgba(255, 255, 255, 0.05)`, + }, + + [':nth-of-type(2)']: { + top: '0px', + right: '0px', + height: '100%', + width: '57px', + background: `rgba(255, 255, 255, 0.05)`, + }, + + [':nth-of-type(3)']: { + top: '0px', + right: '0px', + height: '100%', + width: '188.5px', + boxSizing: 'border-box', + borderStyle: 'solid', + borderWidth: '0px 0px 240px 131.5px', + borderColor: 'transparent transparent rgba(255, 255, 255, 0.05) transparent ', + }, +}); + +export { + BannerContainer, + BannerItemContainer, + BannerItemContent, + BannerItemTextGroup, + BannerItemButton, + BannerItemIndex, + BannerItemDecoration, +}; diff --git a/src/components/Page/Home/Banner/Banner.tsx b/src/components/Page/Home/Banner/Banner.tsx new file mode 100644 index 00000000..375ed376 --- /dev/null +++ b/src/components/Page/Home/Banner/Banner.tsx @@ -0,0 +1,138 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import useRouter from '@router/useRouter'; +import { theme } from '@styles/themes'; +import { + BannerContainer, + BannerItemButton, + BannerItemContainer, + BannerItemContent, + BannerItemDecoration, + BannerItemIndex, + BannerItemTextGroup, +} from './Banner.Style'; + +const HomeBanner = () => { + const { navToAbout, openInstagram, openServiceCenter } = useRouter(); + + const banners = [ + { + title: '인간지표 앱 출시', + sub: '보다 더 편리하게 사용해보세요', + button: { + text: '인간지표 SNS', + onClick: openInstagram, + }, + background: theme.colors.sub_blue6, + }, + { + title: '인간지표, 더 좋아질 수\n있게 도와주세요', + sub: '', + button: { + text: '불편사항 접수', + onClick: openServiceCenter, + }, + background: theme.colors.sub_blue5, + }, + { + title: '인간지표는 어떻게\n활용할 수 있나요?', + sub: '', + button: { + text: '서비스 가이드', + onClick: navToAbout, + }, + background: theme.colors.sub_gray9, + }, + ]; + + const containerRef = useRef(null); + const [currentIndex, setCurrentIndex] = useState(0); + const isAutoScrolling = useRef(false); // 자동 스크롤 중인지 구분 + const intervalRef = useRef(null); // 타이머 ref 추가 + + // 타이머 시작 함수 + const startTimer = useCallback(() => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + intervalRef.current = setInterval(() => { + setCurrentIndex((prev) => (prev + 1) % banners.length); + }, 3000); + }, [banners.length]); + // 컴포넌트 마운트 시 타이머 시작 + useEffect(() => { + startTimer(); + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + }, [startTimer]); + + // 인덱스 변경 시 스크롤 이동 + useEffect(() => { + if (containerRef.current) { + isAutoScrolling.current = true; + const containerWidth = containerRef.current.offsetWidth; + containerRef.current.scrollTo({ + left: containerWidth * currentIndex, + behavior: 'smooth', + }); + + setTimeout(() => { + isAutoScrolling.current = false; + }, 500); + } + }, [currentIndex]); + + // 사용자 수동 스크롤 시 인덱스 업데이트 및 타이머 리셋 + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + let scrollTimeout: NodeJS.Timeout; + + const handleScroll = () => { + if (isAutoScrolling.current) return; + + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { + const containerWidth = container.offsetWidth; + const scrollLeft = container.scrollLeft; + const newIndex = Math.round(scrollLeft / containerWidth); + + if (newIndex >= 0 && newIndex < banners.length) { + setCurrentIndex(newIndex); + startTimer(); // 타이머 리셋! + } + }, 100); + }; + container.addEventListener('scroll', handleScroll); + return () => { + container.removeEventListener('scroll', handleScroll); + clearTimeout(scrollTimeout); + }; + }, [banners.length, startTimer]); // startTimer 의존성 추가 + + return ( + + {banners.map((e, idx, arr) => ( + + + +

    {e.title}

    +

    {e.sub}

    +
    + {e.button.text} → +
    + + {idx + 1} / {arr.length} + + + + +
    + ))} +
    + ); +}; +export default HomeBanner; diff --git a/src/components/Page/Home/Common.Style.ts b/src/components/Page/Home/Common.Style.ts new file mode 100644 index 00000000..680812c2 --- /dev/null +++ b/src/components/Page/Home/Common.Style.ts @@ -0,0 +1,44 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const HomeItemContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '16px', +}); + +const HomeItemTitle = styled.div({ + display: 'flex', + padding: '0px 20px', + gap: '6px', + alignItems: 'center', + + ['>p']: { + margin: '0px', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + + ['&.title']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_gray2, + flexShrink: '0', + }, + + ['&.update-time']: { + ...theme.font.body14Regular, + color: theme.colors.sub_gray8, + marginLeft: 'auto', + }, + }, + + ['>svg']: { + width: '18px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray6, + flexShrink: '0', + }, +}); + +export { HomeItemContainer, HomeItemTitle }; diff --git a/src/components/Page/Home/Common.tsx b/src/components/Page/Home/Common.tsx new file mode 100644 index 00000000..3cc67cbb --- /dev/null +++ b/src/components/Page/Home/Common.tsx @@ -0,0 +1,26 @@ +import { STOCK_UPDATE_TIME } from '@ts/Constants'; +import { StockCountryKey } from '@ts/StockCountry'; +import InfoSVG from '@assets/icons/info.svg?react'; +import { HomeItemTitle } from './Common.Style'; + +const ItemTitle = ({ + title, + openAboutModal, + country, +}: { + title: string; + openAboutModal?: () => void; + country: StockCountryKey; +}) => { + const updateTime = STOCK_UPDATE_TIME[country]; + + return ( + +

    {title}

    + {openAboutModal && } +

    어제 {updateTime} 기준

    +
    + ); +}; + +export default ItemTitle; diff --git a/src/components/Page/Home/Footer/Footer.Style.ts b/src/components/Page/Home/Footer/Footer.Style.ts new file mode 100644 index 00000000..643a673b --- /dev/null +++ b/src/components/Page/Home/Footer/Footer.Style.ts @@ -0,0 +1,77 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const FooterContainer = styled.div({ + background: theme.colors.sub_gray11, + width: '100%', +}); + +const FooterContents = styled.div({ + display: 'flex', + flexDirection: 'column', + padding: '32px 20px', + boxSizing: 'border-box', + margin: '0 auto', + gap: '24px', + maxWidth: '1200px', +}); + +const FooterTitle = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '12px', + + ['>p']: { + ...theme.font.body18Semibold, + color: theme.colors.sub_white, + margin: '0', + }, + + ['>svg']: { + width: 'auto', + height: '21px', + }, +}); + +const FooterButtonContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '12px', +}); + +const FooterButton = styled.div({ + display: 'flex', + background: theme.colors.sub_black, + padding: '12px 14px', + borderRadius: '8px', + gap: '8px', + cursor: 'pointer', + + ['>svg']: { + width: '24px', + height: 'auto', + aspectRatio: '1 / 1', + }, + + ['>p']: { + ...theme.font.body16Medium, + color: theme.colors.sub_gray3, + margin: '0', + }, +}); + +const FooterIconsContainer = styled.div({ + display: 'flex', + gap: '12px', + alignItems: 'center', + + ['>svg']: { + width: '32px', + height: 'auto', + aspectRatio: '1 / 1', + cursor: 'pointer', + fill: 'white', + }, +}); + +export { FooterContainer, FooterContents, FooterTitle, FooterButtonContainer, FooterButton, FooterIconsContainer }; diff --git a/src/components/Page/Home/Footer/Footer.tsx b/src/components/Page/Home/Footer/Footer.tsx new file mode 100644 index 00000000..59c726cd --- /dev/null +++ b/src/components/Page/Home/Footer/Footer.tsx @@ -0,0 +1,92 @@ +import useRouter from '@router/useRouter'; +import businessSVG from '@assets/footer/footer_business.svg?react'; +import commentSVG from '@assets/footer/footer_comment.svg?react'; +import developerSVG from '@assets/footer/footer_developer.svg?react'; +import dictSVG from '@assets/footer/footer_dict.svg?react'; +import InstagramSVG from '@assets/footer/footer_instagram.svg?react'; +import LinkedInSVG from '@assets/footer/footer_linkedin.svg?react'; +import termSVG from '@assets/footer/footer_term.svg?react'; +import ThreadsSVG from '@assets/footer/footer_threads.svg?react'; +import LogoSVG from '@assets/logo_white.svg?react'; +import { + FooterButton, + FooterButtonContainer, + FooterContainer, + FooterContents, + FooterIconsContainer, + FooterTitle, +} from './Footer.Style'; + +const HomeFooter = () => { + const { openInstagram, openLinkedIn, openThreads, navToAbout, navToTerm, openBusinessProposal, openServiceCenter } = + useRouter(); + + const FooterButtonList = [ + { + svg: dictSVG, + title: '서비스 가이드', + onClick: navToAbout, + }, + { + svg: businessSVG, + title: '비즈니스 제안', + onClick: openBusinessProposal, + }, + { + svg: commentSVG, + title: '고객센터', + onClick: openServiceCenter, + }, + { + svg: termSVG, + title: '서비스 이용약관', + onClick: () => navToTerm('agreeTerm'), + }, + { + svg: developerSVG, + title: '개인정보 처리방침', + onClick: () => navToTerm('agreePrivacy'), + }, + ]; + + const SVGButtonList = [ + { + svg: InstagramSVG, + onClick: openInstagram, + }, + { + svg: LinkedInSVG, + onClick: openLinkedIn, + }, + { + svg: ThreadsSVG, + onClick: openThreads, + }, + ]; + + return ( + + + +

    About

    + +
    + + {FooterButtonList.map((item) => ( + + +

    {item.title}

    +
    + ))} +
    + + {SVGButtonList.map((item, index) => ( + + ))} + +
    +
    + ); +}; + +export default HomeFooter; diff --git a/src/components/Page/Home/Header/Header.tsx b/src/components/Page/Home/Header/Header.tsx new file mode 100644 index 00000000..242d7b81 --- /dev/null +++ b/src/components/Page/Home/Header/Header.tsx @@ -0,0 +1,95 @@ +import styled from '@emotion/styled'; +import { SearchCategoryKey } from '@ts/SearchCategory'; +import useAuthInfo from '@hooks/useAuthInfo'; +import useRouter from '@router/useRouter'; +import { useUnreadCountQuery } from '@controllers/notification/query'; +import { theme } from '@styles/themes'; +import AlarmSVG from '@assets/icons/alarm.svg?react'; +import QuestionMarkCircleSVG from '@assets/icons/question_mark_circle.svg?react'; +import SearchSVG from '@assets/icons/search.svg?react'; +import FullLogoWhiteSVG from '@assets/logo/full_logo_white.svg?react'; + +const HomeHeaderContainer = styled.div({ + display: 'flex', + padding: '8px 20px', + width: '100%', + boxSizing: 'border-box', + justifyContent: 'space-between', + alignItems: 'center', + gap: '8px', + + ['>svg']: { + width: '100px', + height: 'auto', + fill: theme.colors.sub_white, + marginRight: 'auto', + }, +}); + +const HomeHeaderButton = styled.div({ + display: 'flex', + gap: '10px', + position: 'relative', + + ['>svg']: { + width: '36px', + height: 'auto', + aspectRatio: '1 / 1', + cursor: 'pointer', + fill: theme.colors.sub_gray7, + }, + + ['&.enable']: { + ['::after']: { + content: '""', + position: 'absolute', + top: '0', + right: '0', + margin: '5px', + display: 'block', + width: '5px', + height: 'auto', + aspectRatio: '1 / 1', + background: theme.colors.sub_red, + borderRadius: '50%', + }, + }, +}); + +const HomeHeader = ({ + openSearchBarModal, +}: { + openSearchBarModal: ({ type, value }?: { type?: SearchCategoryKey; value?: string }) => () => void; +}) => { + const { data: notificationCount } = useUnreadCountQuery(); + const { navToAbout, navToNotification } = useRouter(); + const { isLogin } = useAuthInfo(); + + const handleQuestionMarkClick = () => { + navToAbout(); + }; + + const handleNotificationClick = () => { + navToNotification(); + }; + + return ( + + + + + + + + + + + + + ); +}; + +export default HomeHeader; diff --git a/src/components/Page/Home/Info/Info.Style.ts b/src/components/Page/Home/Info/Info.Style.ts new file mode 100644 index 00000000..36098f97 --- /dev/null +++ b/src/components/Page/Home/Info/Info.Style.ts @@ -0,0 +1,76 @@ +import styled from '@emotion/styled'; +import { deltaToColor } from '@utils/ScoreConvert'; +import { theme } from '@styles/themes'; + +const InfoContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '10px', + padding: '0 20px', +}); + +const InfoScoreContainer = styled.div({ + display: 'flex', + gap: '10px', +}); + +const InfoScoreItemContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + padding: '10px 12px', + background: theme.colors.sub_gray11, + borderRadius: '4px', + width: '100%', +}); + +const InfoScoreItemTitle = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '4px', + + ['>p']: { + margin: '0', + ...theme.font.body16Semibold, + color: theme.colors.sub_gray1, + whiteSpace: 'nowrap', + }, + + ['>svg']: { + width: '16px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray1, + cursor: 'pointer', + }, +}); + +const InfoScoreItemValue = styled.div( + ({ delta }: { delta: number }) => ({ + ['>p']: { + color: deltaToColor(delta) ?? theme.colors.sub_gray1, + }, + + ['>svg']: { + fill: deltaToColor(delta) ?? theme.colors.sub_gray1, + }, + }), + { + display: 'flex', + alignItems: 'center', + gap: '4px', + + ['>p']: { + margin: '0', + ...theme.font.title20Semibold, + whiteSpace: 'nowrap', + }, + + ['>svg']: { + width: '10px', + height: 'auto', + aspectRatio: '1 / 1', + }, + }, +); + +export { InfoContainer, InfoScoreContainer, InfoScoreItemContainer, InfoScoreItemTitle, InfoScoreItemValue }; diff --git a/src/components/Page/Home/Info/Info.tsx b/src/components/Page/Home/Info/Info.tsx new file mode 100644 index 00000000..263bb44f --- /dev/null +++ b/src/components/Page/Home/Info/Info.tsx @@ -0,0 +1,48 @@ +import { StockCountryKey } from '@ts/StockCountry'; +import { deltaToCaret } from '@utils/ScoreConvert'; +import GuideBanner from '@components/Common/GuideBanner/GuideBanner'; +import useAboutFearModal from '@components/Modal/CenterTutorial/AboutFear/useAboutFear'; +import { useIndexScoreQuery } from '@controllers/score/query'; +import ExclamationMarkSVG from '@assets/icons/exclamation_mark_circle.svg?react'; +import { + InfoContainer, + InfoScoreContainer, + InfoScoreItemContainer, + InfoScoreItemTitle, + InfoScoreItemValue, +} from './Info.Style'; + +const HomeInfo = ({ country }: { country: StockCountryKey }) => { + const { data: indexScore } = useIndexScoreQuery(); + + const { Modal: AboutFearModal, openModal: openAboutFearModal } = useAboutFearModal(); + + if (!indexScore) return null; + + return ( + + {AboutFearModal} + + {indexScore[country].map(({ key, name, value, diff }, idx) => { + const Caret = deltaToCaret(diff); + + return ( + + +

    {name}

    + {idx === 0 && openAboutFearModal()} />} +
    + +

    {value}

    + +
    +
    + ); + })} +
    + +
    + ); +}; + +export default HomeInfo; diff --git a/src/components/Page/Home/Keywords/Keywords.Style.ts b/src/components/Page/Home/Keywords/Keywords.Style.ts new file mode 100644 index 00000000..ba9f852e --- /dev/null +++ b/src/components/Page/Home/Keywords/Keywords.Style.ts @@ -0,0 +1,31 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const KeywordsGrid = styled.div({ + display: 'grid', + gridTemplateColumns: 'repeat(3, 1fr)', + gridTemplateRows: 'repeat(3, 1fr)', + rowGap: '12px', + columnGap: '8px', + padding: '0 20px', +}); + +const KeywordItem = styled.div({ + minWidth: '0', + overflow: 'hidden', + padding: '12px', + backgroundColor: theme.colors.sub_gray11, + borderRadius: '8px', + + ['>p']: { + ...theme.font.body16Semibold, + color: theme.colors.sub_white, + margin: '0', + textAlign: 'center', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + overflow: 'hidden', + }, +}); + +export { KeywordsGrid, KeywordItem }; diff --git a/src/components/Page/Home/Keywords/Keywords.tsx b/src/components/Page/Home/Keywords/Keywords.tsx new file mode 100644 index 00000000..01651cba --- /dev/null +++ b/src/components/Page/Home/Keywords/Keywords.tsx @@ -0,0 +1,38 @@ +import { SearchCategoryKey } from '@ts/SearchCategory'; +import { StockCountryKey } from '@ts/StockCountry'; +import useAboutKeyword from '@components/Modal/CenterTutorial/AboutKeyword/useAboutKeyword'; +import { usePopularKeywordsQuery } from '@controllers/stocks/query'; +import ItemTitle from '../Common'; +import { HomeItemContainer } from '../Common.Style'; +import { KeywordItem, KeywordsGrid } from './Keywords.Style'; + +const HomeKeywords = ({ + country, + openSearchBarModal, +}: { + country: StockCountryKey; + openSearchBarModal: ({ type, value }: { type?: SearchCategoryKey; value?: string }) => () => void; +}) => { + const { data: keywords } = usePopularKeywordsQuery(country); + + const { Modal: AboutKeywordModal, openModal: openAboutKeywordModal } = useAboutKeyword(); + + return ( + + {AboutKeywordModal} + openAboutKeywordModal()} /> + + {keywords?.slice(0, 9).map((keyword: string) => ( + +

    {keyword}

    +
    + ))} +
    +
    + ); +}; + +export default HomeKeywords; diff --git a/src/components/Page/Home/Ranking/Ranking.Style.ts b/src/components/Page/Home/Ranking/Ranking.Style.ts new file mode 100644 index 00000000..5046c250 --- /dev/null +++ b/src/components/Page/Home/Ranking/Ranking.Style.ts @@ -0,0 +1,174 @@ +import styled from '@emotion/styled'; +import { deltaToColor } from '@utils/ScoreConvert'; +import { theme } from '@styles/themes'; + +const RankingContent = styled.div({ + padding: '0 20px', + display: 'flex', + flexDirection: 'column', + gap: '24px', + position: 'relative', + minHeight: '446px', +}); + +const RankingTabContainer = styled.div({ + display: 'flex', + gap: '8px', + padding: '0px 8px', +}); + +const RankingTabLabel = styled.label({ + display: 'flex', + gap: '8px', + width: '100%', + + ['>input']: { + display: 'none', + }, + + ['>span']: { + ...theme.font.body16Medium, + color: theme.colors.sub_gray6, + background: theme.colors.sub_gray11, + textAlign: 'center', + width: '100%', + padding: '8px 0px', + borderRadius: '8px', + }, + + ['> input[type="radio"]:checked']: { + ['~span']: { + color: theme.colors.sub_white, + background: theme.colors.sub_blue6, + }, + }, +}); + +const RankingTable = styled.table({ + width: '100%', + borderCollapse: 'collapse', + + ['>thead>tr, >tbody>tr']: { + display: 'grid', + gridTemplateColumns: 'repeat(3, 1fr)', + }, + + ['>thead>tr>th']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + }, +}); + +const RankingStockContainer = styled.tr({ + padding: '9px 0', + + ['&:not(:last-of-type)']: { + borderBottom: `1px solid ${theme.colors.grayscale90}`, + }, +}); + +const RankingStockSymbol = styled.td({ + display: 'flex', + alignItems: 'center', + gap: '8px', + minWidth: '0', + + ['>img']: { + width: '26px', + height: '26px', + flexShrink: '0', + aspectRatio: '1 / 1', + borderRadius: '50%', + }, + + ['>p']: { + ...theme.font.body14Medium, + color: theme.colors.primary0, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + margin: '0', + }, +}); + +const RankingStockPrice = styled.td( + ({ delta }: { delta: number }) => ({ + ['>p.diff']: { + color: deltaToColor(delta) ?? theme.colors.sub_gray7, + }, + }), + { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + + ['>p']: { + margin: '0', + + ['&.price']: { + ...theme.font.body14Medium, + color: theme.colors.primary0, + }, + + ['&.diff']: { + ...theme.font.detail12Medium, + }, + }, + }, +); + +const RankingStockScore = styled.td( + ({ delta }: { delta: number }) => ({ + ['>p.diff']: { + color: deltaToColor(delta) ?? theme.colors.sub_gray7, + }, + }), + { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '2px', + + ['>p']: { + margin: '0', + + ['&.score']: { + ...theme.font.body16Semibold, + color: theme.colors.primary0, + }, + + ['&.diff']: { + ...theme.font.detail12Medium, + }, + }, + }, +); + +const RankingLoading = styled.div({ + position: 'absolute', + bottom: '0', + top: '64px', + left: '0', + right: '0', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + padding: '32px', + + ['>img']: { + width: '128px', + aspectRatio: '1 / 1', + }, +}); + +export { + RankingContent, + RankingTabContainer, + RankingTabLabel, + RankingTable, + RankingStockContainer, + RankingStockSymbol, + RankingStockPrice, + RankingStockScore, + RankingLoading, +}; diff --git a/src/components/Page/Home/Ranking/Ranking.tsx b/src/components/Page/Home/Ranking/Ranking.tsx new file mode 100644 index 00000000..b7bd726e --- /dev/null +++ b/src/components/Page/Home/Ranking/Ranking.tsx @@ -0,0 +1,80 @@ +import { useState } from 'react'; +import { STOCK_COUNTRY_MAP, StockCountryKey } from '@ts/StockCountry'; +import useRouter from '@router/useRouter'; +import { useStockTableInfoQuery } from '@controllers/stocks/query'; +import { StockTableInfo } from '@controllers/stocks/types'; +import LoadingGIF from '@assets/loading.gif'; +import ItemTitle from '../Common'; +import { HomeItemContainer } from '../Common.Style'; +import { RankingContent, RankingLoading, RankingTabContainer, RankingTabLabel, RankingTable } from './Ranking.Style'; +import RankingStock from './RankingStock'; + +export type StockTableTabKey = 'MARKET' | 'VOLUME' | 'RISING' | 'DESCENT'; + +const STOCK_TABLE_TABS: { key: StockTableTabKey; text: string }[] = [ + { key: 'MARKET', text: '거래대금' }, + { key: 'VOLUME', text: '거래량' }, + { key: 'RISING', text: '급상승' }, + { key: 'DESCENT', text: '급하락' }, +]; + +const HomeRanking = ({ country }: { country: StockCountryKey }) => { + const { navToStock } = useRouter(); + const [activeTab, setActiveTab] = useState('MARKET'); + + const currencySymbol = STOCK_COUNTRY_MAP[country].currency; + + const { data: stocks, isLoading } = useStockTableInfoQuery(activeTab, country); + + const handleClickStock = (symbolName: string) => () => navToStock(symbolName, country); + + const handleTabChange = (e: React.ChangeEvent) => setActiveTab(e.target.value as StockTableTabKey); + + return ( + + + + + {STOCK_TABLE_TABS.map(({ key, text }) => ( + + + {text} + + ))} + + + + + 종목 + 주가 + 인간지표 + + + + {stocks?.map((stock: StockTableInfo) => ( + + ))} + + + {isLoading && ( + + + + )} + + + ); +}; + +export default HomeRanking; diff --git a/src/components/Page/Home/Ranking/RankingStock.tsx b/src/components/Page/Home/Ranking/RankingStock.tsx new file mode 100644 index 00000000..28e0c020 --- /dev/null +++ b/src/components/Page/Home/Ranking/RankingStock.tsx @@ -0,0 +1,42 @@ +import { diffToPercent, diffToValue } from '@utils/ScoreConvert'; +import StockImage from '@components/Common/StockImage'; +import { StockTableInfo } from '@controllers/stocks/types'; +import { RankingStockContainer, RankingStockPrice, RankingStockScore, RankingStockSymbol } from './Ranking.Style'; + +const RankingStock = ({ + stock, + handleClickStock, + currencySymbol, +}: { + stock: StockTableInfo; + handleClickStock: (symbolName: string) => () => void; + currencySymbol: string; +}) => { + const priceText = `${currencySymbol}${stock.price.toLocaleString()}`; + const priceDiffText = `${diffToValue(stock.priceDiff)}(${diffToPercent(stock.price, stock.priceDiff, { + fixed: 2, + sign: false, + })})`; + + const scoreText = `${stock.score}점`; + const scoreDiffText = `(${diffToValue(stock.scoreDiff)}점)`; + + return ( + + + +

    {stock.symbolName}

    +
    + +

    {priceText}

    +

    {priceDiffText}

    +
    + +

    {scoreText}

    +

    {scoreDiffText}

    +
    +
    + ); +}; + +export default RankingStock; diff --git a/src/components/Page/Lab/Common.Style.ts b/src/components/Page/Lab/Common.Style.ts new file mode 100644 index 00000000..09c627fc --- /dev/null +++ b/src/components/Page/Lab/Common.Style.ts @@ -0,0 +1,74 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const ResultItemContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '10px', + margin: '0 20px', +}); + +const ResultItemTitle = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '10px', + + ['>p']: { + margin: '0', + color: theme.colors.sub_white, + whiteSpace: 'pre', + + ['&.title']: { + ...theme.font.title20Semibold, + }, + + ['&.description']: { + ...theme.font.body18Medium, + wordBreak: 'keep-all', + + ['&.small']: { + ...theme.font.body14Medium, + }, + }, + }, +}); + +const ResultItemTitleHighlight = styled.span( + ({ type, color, background }: { type: 'RECOMMEND' | 'ZIPYO' | 'PATTERN'; color?: string; background?: string }) => ({ + color: type === 'ZIPYO' ? color : theme.colors.sub_white, + background: + type === 'RECOMMEND' ? `${theme.colors.sub_white}1A` : type == 'PATTERN' ? theme.colors.sub_blue6 : background, + + ...(type === 'RECOMMEND' ? theme.font.body14Semibold : theme.font.body16Semibold), + }), + { + display: 'inline-block', + padding: '4px 12px', + borderRadius: '999px', + marginRight: '6px', + }, +); + +const ResultItemHelpContainer = styled.div({ + ...theme.font.body16Medium, + color: theme.colors.sub_white, + display: 'flex', + alignItems: 'center', + gap: '4px', + marginLeft: 'auto', + + ['>svg']: { + width: '14px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray9, + }, + ['>p']: { + margin: '0', + ...theme.font.detail10Medium, + color: theme.colors.sub_gray8, + textDecoration: 'underline', + }, +}); + +export { ResultItemContainer, ResultItemTitle, ResultItemTitleHighlight, ResultItemHelpContainer }; diff --git a/src/components/Page/Lab/ResultPattern/ResultPattern.tsx b/src/components/Page/Lab/ResultPattern/ResultPattern.tsx new file mode 100644 index 00000000..c5e88925 --- /dev/null +++ b/src/components/Page/Lab/ResultPattern/ResultPattern.tsx @@ -0,0 +1,306 @@ +import styled from '@emotion/styled'; +import { getDiffText } from '@utils/Number'; +import { deltaToCaret, deltaToColor } from '@utils/ScoreConvert'; +import ReportPatternChart from '@components/Lab/ReportPatternChart/ReportPatternChart'; +import { PortfolioResultPattern } from '@controllers/experiment/api'; +import { theme } from '@styles/themes'; +import HelpSVG from '@assets/icons/question_mark_circle_fill.svg?react'; +import { + ResultItemContainer, + ResultItemHelpContainer, + ResultItemTitle, + ResultItemTitleHighlight, +} from '../Common.Style'; + +export type PatternQuadrantKey = 'trend-preemptive' | 'lagging-follower' | 'reverse-investor' | 'value-preemptive'; + +export const patternQuadrantKeys: PatternQuadrantKey[] = [ + 'trend-preemptive', + 'lagging-follower', + 'reverse-investor', + 'value-preemptive', +]; + +export interface PatternQuadrant { + icon: string; + name: string; + buySignal: string; + outcome: '수익' | '손실'; + copy: { + primary: string; + secondary: string; + }; +} + +export const patternQuadrantMap: Record = { + 'trend-preemptive': { + icon: '✅', + name: '트렌드 선점형', + buySignal: '인간지표 높을 때 매수', + outcome: '수익', + copy: { + primary: '점수가 높을 때 매수하여, 수익을 보는 투자 패턴', + secondary: '군중심리 활용을 잘하고 있는 유형이에요!', + }, + }, + 'lagging-follower': { + icon: '❕', + name: '후행 추종형', + buySignal: '인간지표 높을 때 매수', + outcome: '손실', + copy: { + primary: '점수가 높을 때 매수하여, 손실을 보는 투자 패턴', + secondary: '유행을 따라가다 시장이 과열되어 있어 물리게되는 경우가 많아요!', + }, + }, + 'reverse-investor': { + icon: '📉', + name: '역행 투자형', + buySignal: '인간지표 낮을 때 매수', + outcome: '손실', + copy: { + primary: '점수가 낮을 때 매수하여, 손실을 보는 투자 패턴', + secondary: '남들과 반대로 하다 실패하는 경우가 많아요!', + }, + }, + 'value-preemptive': { + icon: '💎', + name: '가치 선점형', + buySignal: '인간지표 낮을 때 매수', + outcome: '수익', + copy: { + primary: '점수가 낮을 때 매수하여, 수익을 보는 투자 패턴', + secondary: '남들이 관심 없을 때 진입을 해두는 경우가 많아요!', + }, + }, +}; + +export const patternQuadrantList: ({ + key: PatternQuadrantKey; +} & PatternQuadrant)[] = Object.entries(patternQuadrantMap).map( + ([key, value]) => + ({ + key, + ...value, + }) as { key: PatternQuadrantKey } & PatternQuadrant, +); + +const PatternExplainContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '4px', + + padding: '16px 10px', + background: `${theme.colors.sub_white}0D`, + borderRadius: '4px', + + ['>p']: { + margin: '0', + + ['&.primary']: { + ...theme.font.body14Semibold, + color: theme.colors.sub_gray2, + }, + ['&.secondary']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray7, + }, + }, +}); + +const PatternExplainTitle = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '4px', + + ['>p']: { + margin: '0', + + ['&.title']: { + ...theme.font.body14Semibold, + color: theme.colors.sub_blue5, + }, + ['&.sub']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray6, + }, + }, +}); + +const PatternHistoryContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '8px', + borderRadius: '4px', + padding: '8px 10px 12px', + background: `${theme.colors.sub_white}0D`, +}); + +const PatternHistoryHeader = styled.div({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + + ['>p']: { + margin: 0, + + ['&.title']: { + ...theme.font.body14Semibold, + color: theme.colors.sub_gray6, + }, + ['&.sub']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray7, + }, + }, +}); + +const PatternHistoryContent = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '8px', +}); + +const PatternHistoryItem = styled.div( + ({ delta }: { delta: number }) => ({ + ['span.roi']: { + ['>p']: { + color: deltaToColor(delta) ?? theme.colors.sub_gray6, + }, + ['>svg']: { + fill: deltaToColor(delta) ?? theme.colors.sub_gray6, + }, + }, + }), + { + display: 'flex', + flexDirection: 'column', + background: `${theme.colors.sub_white}05`, + borderRadius: '4px', + padding: '8px 10px', + border: `1px solid ${theme.colors.sub_gray10}`, + gap: '4px', + + ['>div']: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + gap: '4px', + + ['p']: { + margin: '0', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + + ['&.primary']: { + ['>p.name']: { + ...theme.font.body16Medium, + color: theme.colors.sub_gray2, + }, + ['>span.roi']: { + display: 'flex', + alignItems: 'center', + gap: '4px', + + ['>p']: { + ...theme.font.body14Semibold, + }, + + ['>svg']: { + width: '10px', + height: 'auto', + }, + }, + }, + ['&.secondary']: { + ['>p.date']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray7, + }, + ['>p.score']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray6, + background: theme.colors.sub_gray10, + borderRadius: '5px', + padding: '2px 4px', + }, + }, + }, + }, +); + +const LabResultPattern = ({ + pattern, + openHelpModal, +}: { + pattern?: PortfolioResultPattern; + openHelpModal: () => void; +}) => { + if (!pattern) return null; + + const { type, percentile, history } = pattern; + + const patternQuadrant = patternQuadrantMap[type]; + const { icon, name, copy } = patternQuadrant; + + return ( + + +

    그동안 지켜본 당신의 투자패턴은

    +

    + + {icon} {name} + + 에 속하는 + 경우가 많아요 +

    +
    + + +

    + {icon} {name} 이란? +

    +

    ({percentile}% 유저가 이에 속해요)

    +
    +

    {copy.primary}

    +

    {copy.secondary}

    +
    + openHelpModal()}> + +

    각 사분면은 무슨 패턴이에요?

    +
    + + + +

    상세 데이터({history.length})

    +

    최근 완료순

    +
    + + {history.map((e, idx) => { + const Caret = deltaToCaret(e.roi); + + return ( + +
    +

    {e.stockName}

    + + +

    {getDiffText({ percentDiff: e.roi, option: { percentFixed: 1 } })}

    +
    +
    +
    +

    XX.XX.XX ~ XX.XX.XX

    +

    인간지표 {e.score}

    +
    +
    + ); + })} +
    +
    +
    + ); +}; + +export default LabResultPattern; diff --git a/src/components/Page/Lab/ResultRecommend/ResultRecommend.tsx b/src/components/Page/Lab/ResultRecommend/ResultRecommend.tsx new file mode 100644 index 00000000..444499ac --- /dev/null +++ b/src/components/Page/Lab/ResultRecommend/ResultRecommend.tsx @@ -0,0 +1,134 @@ +import styled from '@emotion/styled'; +import { getDiffText } from '@utils/Number'; +import { PortfolioResultRecommend } from '@controllers/experiment/api'; +import { theme } from '@styles/themes'; +import { ResultItemContainer, ResultItemTitle, ResultItemTitleHighlight } from '../Common.Style'; + +const RecommendTable = styled.div({ + display: 'table', + padding: '12px 10px', + background: `${theme.colors.sub_white}0D`, + borderRadius: '4px', + + ['>div']: { + display: 'table-row', + + ['&.header>p']: { + color: theme.colors.sub_gray6, + fontSize: '13px', + }, + + ['&.row>p']: { + padding: '15px 20px 7px', + }, + }, + + ['>div>p']: { + display: 'table-cell', + ...theme.font.detail12Medium, + textAlign: 'center', + }, + + ['>div.row:not(:last-child) > p']: { + borderBottom: `1px solid ${theme.colors.sub_gray10}`, + }, +}); + +const RecommendSummary = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '8px', + + ['>p']: { + margin: '0', + + ['&.primary']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray4, + }, + + ['&.secondary']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray5, + + ['>b']: { + color: theme.colors.sub_white, + }, + }, + }, +}); + +const LabResultRecommend = ({ recommend }: { recommend?: PortfolioResultRecommend }) => { + if (!recommend) return null; + + const { scoreTable, weeklyExperimentCount } = recommend; + + const focusRange = scoreTable.reduce((prev, curr) => { + if (curr.avgYieldUser > prev.avgYieldUser) { + return curr; + } + return prev; + }, scoreTable[0]); + + const [lowestProfit, highestProfit] = ['min', 'max'].map((initial) => + scoreTable.reduce( + (acc, curr) => { + if (initial === 'min' ? curr.avgYieldUser > acc.value : curr.avgYieldUser < acc.value) { + return acc; + } + return { + range: curr.min === 90 ? '90점 이상' : `${curr.min}~${curr.max}점`, + value: curr.avgYieldUser, + }; + }, + { + range: '', + value: initial === 'min' ? Infinity : -Infinity, + }, + ), + ); + + return ( + + +

    + 인간지표로 보는 내 매수 타이밍 잡는 법! +

    +

    + 다음 매수 때는, + + ✨{focusRange.min} + {focusRange.max !== 100 ? `~${focusRange.max}점` : '점 이상'} 구간 + + 에 주목해보세요! +

    +
    + +
    +

    인간지표 점수대

    +

    전체 평균 수익률

    +

    내 평균 수익률

    +
    + {scoreTable.map((e, idx) => ( +
    +

    + {e.min} + {e.max === 100 ? '점 이상' : `-${e.max}점`} +

    +

    {getDiffText({ percentDiff: e.avgYieldTotal, option: { percentFixed: 1 } })}

    +

    {getDiffText({ percentDiff: e.avgYieldUser, option: { percentFixed: 1 } })}

    +
    + ))} +
    + +

    이번주에 총 {weeklyExperimentCount}건의 실험을 진행하셨습니다.

    +

    + ☺️ 가장 높은 수익률 | {highestProfit.range} 구간
    + 😭 가장 낮은 수익률 | {lowestProfit.range} 구간 +

    +
    +
    + ); +}; + +export default LabResultRecommend; diff --git a/src/components/Page/Lab/ResultZipyo/ResultZipyo.tsx b/src/components/Page/Lab/ResultZipyo/ResultZipyo.tsx new file mode 100644 index 00000000..410302b8 --- /dev/null +++ b/src/components/Page/Lab/ResultZipyo/ResultZipyo.tsx @@ -0,0 +1,79 @@ +import styled from '@emotion/styled'; +import useAuthInfo from '@hooks/useAuthInfo'; +import ReportClassChart from '@components/Lab/ReportClassChart/ReportClassChart'; +import { reportClassMap } from '@components/Lab/ReportClassChart/ReportClassChart.Type'; +import { PortfolioResultHumanIndicator } from '@controllers/experiment/api'; +import { theme } from '@styles/themes'; +import HelpSVG from '@assets/icons/question_mark_circle_fill.svg?react'; +import { + ResultItemContainer, + ResultItemHelpContainer, + ResultItemTitle, + ResultItemTitleHighlight, +} from '../Common.Style'; + +const ZipyoChartContainer = styled.div({ + background: `${theme.colors.sub_white}0D`, + padding: '12px 10px', + borderRadius: '4px', +}); + +const ZipyoSummary = styled.p({ + ...theme.font.body14Medium, + color: theme.colors.sub_gray1, + margin: '0', + wordBreak: 'keep-all', + textIndent: '-0.5em', + paddingLeft: '0.5em', + + ['>span']: { + color: theme.colors.sub_white, + background: `${theme.colors.sub_white}1A`, + padding: '4px 12px', + borderRadius: '999px', + }, +}); + +const LabResultZipyo = ({ + humanIndicator, + openHelpModal, +}: { + humanIndicator?: PortfolioResultHumanIndicator; + openHelpModal: () => void; +}) => { + const { userInfo } = useAuthInfo(); + + if (!humanIndicator) return null; + + const { type, successRate, percentile, totalBuyCount, successCount } = humanIndicator; + + const reportClass = reportClassMap[type]; + const { icon, title, color, background } = reportClass; + + return ( + + +

    나의 인간지표는?

    +

    + {userInfo?.nickname}님은 + + {icon} {title} + + 지표! +

    +
    + openHelpModal()}> + +

    다른 유형은 뭐가 있어요?

    +
    + + + + + {totalBuyCount}개 종목을 사서, {successCount}개 오르는 당신의 유형! + +
    + ); +}; + +export default LabResultZipyo; diff --git a/src/components/Page/ShortView/AppInduce/AppInduce.tsx b/src/components/Page/ShortView/AppInduce/AppInduce.tsx new file mode 100644 index 00000000..4f3f6baa --- /dev/null +++ b/src/components/Page/ShortView/AppInduce/AppInduce.tsx @@ -0,0 +1,106 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; +import ShortViewAppPNG from '@assets/design/shortViewApp.png'; + +const AppInduceContainer = styled.div({ + width: '100%', + height: 'calc(100% - 96px)', + position: 'fixed', + background: 'linear-gradient(180deg, rgba(16, 16, 16, 0.4) 0%, #101010 47.97%)', + backdropFilter: 'blur(2.5px)', + zIndex: '10', + top: '0', + left: '0', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + padding: '20px', + boxSizing: 'border-box', +}); + +const AppInduceTitle = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '12px', + padding: '36px 0', + + ['>p']: { + margin: '0', + textAlign: 'center', + + ['&.title']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_white, + }, + + ['&.description']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray4, + }, + }, +}); + +const AppInduceContent = styled.div({ + display: 'flex', + flexDirection: 'column', + width: '100%', + maxWidth: '300px', + padding: '0 16px', + boxSizing: 'border-box', + alignItems: 'center', + + ['>img']: { + width: '215px', + height: 'auto', + filter: 'drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.8))', + }, + + ['button']: { + position: 'relative', + ...theme.font.body18Semibold, + color: theme.colors.sub_gray11, + background: theme.colors.sub_white, + borderRadius: '999px', + padding: '10px 28px', + border: 'none', + width: '100%', + }, + + ['>p']: { + ...theme.font.body14Regular, + color: theme.colors.sub_gray6, + margin: '12px 0 0', + }, +}); + +const ShortViewAppInduce = () => { + const handleDownload = () => { + // TODO: 실제 앱 다운로드 링크로 이동 + // 예: window.location.href = 'https://apps.apple.com/...' 또는 'https://play.google.com/...' + }; + + return ( + + +

    + 인간지표 앱을 설치하고 +
    더 다양한 기능을 사용해보세요! +

    +

    숏뷰 기능은 앱에서만 사용가능해요

    +
    + + + +

    괜찮아요 모바일 웹으로 볼게요

    +
    +
    + ); +}; + +export default ShortViewAppInduce; diff --git a/src/components/Page/ShortView/Empty/Empty.tsx b/src/components/Page/ShortView/Empty/Empty.tsx new file mode 100644 index 00000000..b5d9a42e --- /dev/null +++ b/src/components/Page/ShortView/Empty/Empty.tsx @@ -0,0 +1,71 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; +import AlertSVG from '@assets/icons/alert.svg?react'; +import LoadingGIF from '@assets/loading.gif'; + +const ShortViewEmptyContainer = styled.div({ + position: 'relative', + + ['>svg']: { + position: 'absolute', + }, +}); + +const ShortViewEmptyContent = styled.div({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '16px', + ['>p']: { + margin: '0', + ...theme.font.body16Semibold, + color: theme.colors.sub_gray4, + }, + ['>button']: { + ...theme.font.body14Bold, + color: theme.colors.sub_gray4, + background: theme.colors.sub_gray10, + borderRadius: '8px', + padding: '4px 12px', + marginTop: '-8px', + border: 'none', + cursor: 'pointer', + }, + ['>svg, >img']: { + width: '80px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_blue6, + }, +}); + +const ShortViewEmpty = ({ + isShow, + isLoading, + fetchMore, +}: { + isShow: boolean; + isLoading: boolean; + fetchMore: () => void; +}) => { + const LoadingComponent = () => ( + + +

    새로운 종목을 불러오는 중...

    +
    + ); + + const ErrorComponent = () => ( + + +

    종목을 불러오는 데 실패했어요😭

    + +
    + ); + + if (!isShow) return null; + + return {isLoading ? : }; +}; + +export default ShortViewEmpty; diff --git a/src/components/Page/ShortView/Tutorial/Tutorial.Style.ts b/src/components/Page/ShortView/Tutorial/Tutorial.Style.ts new file mode 100644 index 00000000..beafda5d --- /dev/null +++ b/src/components/Page/ShortView/Tutorial/Tutorial.Style.ts @@ -0,0 +1,303 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const TutorialContainer = styled.div({ + width: '100%', + height: '100%', + position: 'fixed', + background: 'rgba(0, 0, 0, 0.8)', + backdropFilter: 'blur(4px)', + zIndex: '1000', + top: '0', + left: '0', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + + ['>div']: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '24px', + overflow: 'auto', + overscrollBehavior: 'contain', + padding: '20px', + flexGrow: 1, + justifyContent: 'center', + }, +}); + +const TutorialContent = styled.div({ + display: 'flex', + flexShrink: '0', + overflow: 'auto', + scrollSnapType: 'x mandatory', + scrollSnapStop: 'always', + scrollBehavior: 'smooth', + gap: '24px', + flexGrow: 1, + maxHeight: '560px', + + msOverflowStyle: 'none', + ['::-webkit-scrollbar']: { + display: 'none', + }, +}); + +const TutorialContentSlideButtonContainer = styled.div({ + position: 'absolute', + height: '100px', + left: '50%', + bottom: '55%', + transform: 'translateX(-50%)', + width: '480px', + + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + zIndex: '10', + + ['@media (max-width: 480px)']: { + display: 'none', + }, + + ['>svg']: { + width: '48px', + height: 'auto', + aspectRatio: '1 / 1', + cursor: 'pointer', + + ['&.left']: { + transform: 'scaleX(-1)', + }, + ['&.right']: { + transform: 'scaleX(1)', + }, + }, +}); + +const TutorialItem = styled.div({ + flexShrink: '0', + width: '100%', + scrollSnapAlign: 'center', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '48px', + padding: '0px 20px', + boxSizing: 'border-box', +}); + +const TutorialStep = styled.div({ + display: 'flex', + gap: '10px', + + ['>span']: { + width: '8px', + height: '8px', + background: theme.colors.sub_gray4, + borderRadius: '50%', + opacity: '0.3', + + ['&.current']: { + background: theme.colors.sub_blue6, + opacity: '1', + }, + }, +}); + +const TutorialItemContent = styled.div({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + position: 'relative', + flexGrow: 1, +}); + +const TutorialItemTinderCard = styled.div({ + position: 'absolute', + height: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + + ['>img:first-of-type']: { + position: 'relative', + height: '100%', + objectFit: 'contain', + borderRadius: '8px', + boxShadow: '0px 4px 40px 0px rgba(255, 255, 255, 0.1), 0px 4px 4px 0px rgba(0, 0, 0, 0.25)', + border: `1px solid ${theme.colors.sub_gray9}`, + boxSizing: 'border-box', + zIndex: '10', + }, +}); + +const TutorialItemTinderCardShadow = styled.span({ + position: 'absolute', + height: '100%', + background: theme.colors.sub_black, + borderRadius: '8px', + boxShadow: '0px 4px 40px 0px rgba(255, 255, 255, 0.1), 0px 4px 4px 0px rgba(0, 0, 0, 0.25)', + border: `1px solid ${theme.colors.sub_gray9}`, + boxSizing: 'border-box', + + ['&:nth-of-type(1)']: { + bottom: '-12px', + width: '90%', + zIndex: '9', + }, + ['&:nth-of-type(2)']: { + bottom: '-24px', + width: '80%', + zIndex: '8', + }, +}); + +const TutorialItemSwipeHand = styled.img( + ({ isLeft }: { isLeft?: boolean }) => ({ + left: isLeft ? '40%' : 'auto', + right: isLeft ? 'auto' : '40%', + transform: isLeft ? 'scaleX(1) translateX(-90%)' : 'scaleX(-1) translateX(-90%)', + }), + { + width: '75%', + bottom: '0px', + position: 'absolute', + objectFit: 'contain', + zIndex: '10', + }, +); + +const TutorialItemCircleButtonContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '24px', + alignItems: 'center', + position: 'absolute', + + bottom: '0px', + transform: 'translateY(50%)', + zIndex: '11', + + padding: '18px', + borderRadius: '50%', + + ['>svg']: { + width: '28px', + height: '28px', + }, + + ['>span']: { + position: 'absolute', + bottom: 'calc(100% + 18px)', + ...theme.font.detail10Medium, + color: theme.colors.sub_white, + padding: '4px 10px', + borderRadius: '4px', + whiteSpace: 'nowrap', + + ['::before']: { + content: '""', + position: 'absolute', + top: '100%', + left: '50%', + transform: 'translateX(-50%)', + width: '0', + height: '0', + borderLeft: '6px solid transparent', + borderRight: '6px solid transparent', + }, + }, + + ['&.money']: { + background: theme.colors.sub_blue6, + + ['>svg']: { + fill: theme.colors.sub_white, + }, + + ['>span']: { + background: theme.colors.sub_blue6, + + ['::before']: { + borderTop: `8px solid ${theme.colors.sub_blue6}`, + }, + }, + }, + + ['&.cross']: { + background: theme.colors.sub_gray10, + + ['>svg']: { + fill: theme.colors.sub_gray5, + }, + + ['>span']: { + background: theme.colors.sub_gray8, + + ['::before']: { + borderTop: `8px solid ${theme.colors.sub_gray8}`, + }, + }, + }, +}); + +const ButtonContainer = styled.div({ + width: '100%', + display: 'flex', + boxSizing: 'border-box', + padding: '0px 20px', + justifyContent: 'center', + maxWidth: '300px', + + ['>button']: { + width: '100dvw', + + transition: 'opacity 0.3s ease-in-out', + + [':disabled']: { + opacity: '0', + }, + }, +}); + +const TutorialTextContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '24px', + alignItems: 'center', + + ['>p']: { + margin: '0px', + textAlign: 'center', + + ['&.title']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_white, + }, + + ['&.description']: { + ...theme.font.body14Regular, + color: theme.colors.sub_gray4, + whiteSpace: 'pre', + }, + }, +}); + +export { + TutorialContainer, + TutorialContent, + TutorialItem, + TutorialItemTinderCardShadow, + TutorialStep, + TutorialItemContent, + TutorialItemTinderCard, + TutorialItemSwipeHand, + TutorialItemCircleButtonContainer, + ButtonContainer, + TutorialTextContainer, + TutorialContentSlideButtonContainer, +}; diff --git a/src/components/Page/ShortView/Tutorial/Tutorial.tsx b/src/components/Page/ShortView/Tutorial/Tutorial.tsx new file mode 100644 index 00000000..5be6aa0c --- /dev/null +++ b/src/components/Page/ShortView/Tutorial/Tutorial.tsx @@ -0,0 +1,140 @@ +import { useEffect, useRef, useState } from 'react'; +import useLocalStorageState from '@hooks/useLocalStorageState'; +import Button from '@components/Common/Button'; +import CrossSVG from '@assets/icons/cross.svg?react'; +import MoneySVG from '@assets/icons/money.svg?react'; +import ShortViewChevronRightSVG from '@assets/icons/shortview/chevronRight.svg?react'; +import ShortViewMockImage from '@assets/short_view_mock.png'; +import SwipeHandPNG from '@assets/swipe_hand.png'; +import { + ButtonContainer, + TutorialContainer, + TutorialContent, + TutorialContentSlideButtonContainer, + TutorialItem, + TutorialItemCircleButtonContainer, + TutorialItemContent, + TutorialItemSwipeHand, + TutorialItemTinderCard, + TutorialItemTinderCardShadow, + TutorialStep, + TutorialTextContainer, +} from './Tutorial.Style'; + +const TutorialSteps = [ + { + content: ( + + + short view mock + + + + + ), + title: '드래그를 통해 무한 탐색', + description: `아래로 드래그해서 종목을 무한으로 탐색할 수 있어요\n관심 있어 하실만한 종목을 추천해줘요`, + }, + { + content: ( + + + short view mock + + + + + 모의매수 + + + ), + title: '🙆 관심있는 종목은 오른쪽으로!', + description: `이 종목, 곧 오를 것 같다면? 오른쪽으로 드래그해서\n모의매수를 진행해보세요!`, + }, + { + content: ( + + + short view mock + + + + + 다시 안보기 + + + ), + title: '🙅‍♂️ 관심없는 종목은 왼쪽으로! ', + description: `관심 없는 종목은 왼쪽으로 드래그해주세요\n앞으로 추천에서 제외해 드릴께요`, + }, +]; + +const ShortViewTutorial = () => { + const containerRef = useRef(null); + const [, setTutorialWatched] = useLocalStorageState('tutorial_watched_shortview'); + + const handleClickTutorialEnd = () => { + setTutorialWatched(true); + }; + + const [stepIndex, setStepIndex] = useState(0); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + const checkScroll = () => { + const { scrollLeft, clientWidth } = container; + setStepIndex(~~((scrollLeft + clientWidth / 2) / clientWidth)); + }; + + container.addEventListener('scroll', checkScroll); + + return () => { + container.removeEventListener('scroll', checkScroll); + }; + }, []); + + const handleClickSlideButton = (direction: 'left' | 'right') => () => { + const container = containerRef.current; + if (!container) return; + container.scrollTo({ + left: container.scrollLeft + (direction === 'left' ? -container.clientWidth : container.clientWidth), + behavior: 'smooth', + }); + }; + + return ( + +
    + + {TutorialSteps.map(({ content, title, description }, i) => ( + + {content} + +

    {title}

    +

    {description}

    +
    +
    + ))} + + + + +
    + + {TutorialSteps.map((_, i) => ( + + ))} + + + + +
    +
    + ); +}; + +export default ShortViewTutorial; diff --git a/src/components/Page/Stock/Common.Style.ts b/src/components/Page/Stock/Common.Style.ts new file mode 100644 index 00000000..08d9353f --- /dev/null +++ b/src/components/Page/Stock/Common.Style.ts @@ -0,0 +1,9 @@ +import styled from '@emotion/styled'; + +const StockItemContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '10px', +}); + +export { StockItemContainer }; diff --git a/src/components/Page/Stock/Header/Header.Style.ts b/src/components/Page/Stock/Header/Header.Style.ts new file mode 100644 index 00000000..517311af --- /dev/null +++ b/src/components/Page/Stock/Header/Header.Style.ts @@ -0,0 +1,46 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const HeaderContainer = styled.header({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + padding: '8px 20px 16px', + boxSizing: 'border-box', + gap: '12px', + borderBottom: `4px solid ${theme.colors.sub_gray11}`, + + ['>svg']: { + fill: theme.colors.sub_gray5, + width: '32px', + height: 'auto', + aspectRatio: '1 / 1', + }, + + ['>span.grow']: { + flexGrow: 1, + }, +}); + +const HeaderIconButton = styled.button( + ({ isActive }: { isActive?: boolean }) => ({ + ['>svg']: { + fill: isActive ? theme.colors.sub_blue6 : theme.colors.sub_gray7, + width: '36px', + height: 'auto', + aspectRatio: '1 / 1', + }, + }), + { + background: 'none', + border: 'none', + padding: '0', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, +); + +export { HeaderContainer, HeaderIconButton }; diff --git a/src/components/Page/Stock/Header/Header.tsx b/src/components/Page/Stock/Header/Header.tsx new file mode 100644 index 00000000..e437730f --- /dev/null +++ b/src/components/Page/Stock/Header/Header.tsx @@ -0,0 +1,156 @@ +import { MESSAGE_TYPES } from '@config/webview'; +import useAuthInfo from '@hooks/useAuthInfo'; +import useLocalStorageState from '@hooks/useLocalStorageState'; +import useToast from '@hooks/useToast'; +import useRouter from '@router/useRouter'; +import ConfirmModal from '@components/Modal/Confirm/ConfirmModal'; +import Toast from '@components/Toast/Toast'; +import { + useAddBookmarkMutation, + useDeleteBookmarkMutation, + useStockPreferenceQuery, + useToggleNotificationMutation, +} from '@controllers/preference/query'; +import { StockDetailInfo } from '@controllers/stocks/types'; +import ArrowLeftSVG from '@assets/icons/arrowLeft.svg?react'; +import BellSVG from '@assets/icons/bell.svg?react'; +import HeartSVG from '@assets/icons/heart.svg?react'; +import ToastBellSVG from '@assets/icons/toast/bell.svg?react'; +import ToastBellCrossSVG from '@assets/icons/toast/bell_cross.svg?react'; +import ToastHeartSVG from '@assets/icons/toast/heart.svg?react'; +import { HeaderContainer, HeaderIconButton } from './Header.Style'; + +const StockHeader = ({ stockInfo }: { stockInfo: StockDetailInfo }) => { + const { navToBack } = useRouter(); + const { isLogin, handleNavigateLogin } = useAuthInfo(); + const { toast, showToast, hideToast } = useToast(); + + const { data: stockPreference } = useStockPreferenceQuery(stockInfo.stockId); + const { mutate: addBookMark } = useAddBookmarkMutation(); + const { mutate: deleteBookmark } = useDeleteBookmarkMutation(); + const { mutate: toggleNotification } = useToggleNotificationMutation(); + const isBookmark = stockPreference?.isBookmarked ?? false; + const isNotification = stockPreference?.isNotificationEnabled ?? false; + const [accessToken] = useLocalStorageState('access_token'); + + const checkAndRequestNotificationPermission = async () => { + const isWebView = !!(window as any).ReactNativeWebView; + if (isWebView) { + (window as any).ReactNativeWebView.postMessage( + JSON.stringify({ + type: MESSAGE_TYPES.REQUEST_NOTIFICATION_PERMISSION, + token: accessToken, + }), + ); + } + }; + + const onHeartClick = async () => { + if (!stockInfo) return; + if (!isLogin) { + openLoginModal(); + return; + } + + if (!isBookmark) { + // 관심 등록 시 알림 권한 체크 및 요청 + await checkAndRequestNotificationPermission(); + + showToast( + <> + +

    관심 등록 완료! 민심 급변 시 알림 드릴게요

    + , + ); + addBookMark(stockInfo.stockId); + } else { + openDeleteFavoritesModal(); + } + }; + + const onBellClick = async () => { + if (!stockInfo) return; + + if (!isLogin) { + openLoginModal(); + return; + } + + if (!isNotification) { + await checkAndRequestNotificationPermission(); + toggleNotification(stockInfo.stockId); + showToast( + <> + +

    알림이 설정되었어요

    + , + ); + } else { + openOffNotificationModal(); + } + }; + + const handleNotificationDelete = async () => { + closeOffNotificationModal(); + toggleNotification(stockInfo.stockId); + showToast( + <> + +

    알림이 해제되었어요

    + , + ); + }; + + const handleDeleteFavorites = () => { + deleteBookmark(stockInfo.stockId); + closeDeleteFavoritesModal(); + }; + + const [OffNotificationModal, openOffNotificationModal, closeOffNotificationModal] = ConfirmModal({ + title: '알림을 해제할까요?', + description: ( + <> + 관심 종목은 유지된 채, + 알림만 해제돼요 + + ), + onConfirm: handleNotificationDelete, + isInverse: true, + actionText: ['해제하기', '취소'], + }); + + const [DeleteFavoritesModal, openDeleteFavoritesModal, closeDeleteFavoritesModal] = ConfirmModal({ + title: '관심 설정을 해제할까요?', + description: <>변동 알림도 중단돼요, + onConfirm: handleDeleteFavorites, + isInverse: true, + actionText: ['해제하기', '취소'], + }); + + const [LoginModal, openLoginModal] = ConfirmModal({ + title: '관심종목 알림을 받으려면, 로그인이 필요해요!', + description: '관심종목의 심리가 급등/급락할때 알림을 받고싶다면, 로그인을 진행해주세요', + onConfirm: handleNavigateLogin, + isInverse: true, + actionText: ['로그인하기', '취소'], + }); + + return ( + + + + + + + + + + + + + + + ); +}; + +export default StockHeader; diff --git a/src/components/Page/Stock/Header/SearchHeader.Style.ts b/src/components/Page/Stock/Header/SearchHeader.Style.ts new file mode 100644 index 00000000..d38e31af --- /dev/null +++ b/src/components/Page/Stock/Header/SearchHeader.Style.ts @@ -0,0 +1,47 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const SearchHeaderWrapper = styled.header({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + padding: '8px 20px', + boxSizing: 'border-box', + marginBottom: '8px', + + ['>svg']: { + fill: theme.colors.sub_gray5, + width: '32px', + height: 'auto', + aspectRatio: '1 / 1', + }, +}); + +const RightSection = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '12px', +}); + +const IconButton = styled.button( + ({ isActive }: { isActive?: boolean }) => ({ + ['>svg']: { + fill: isActive ? theme.colors.sub_blue6 : theme.colors.sub_gray7, + width: '36px', + height: 'auto', + aspectRatio: '1 / 1', + }, + }), + { + background: 'none', + border: 'none', + padding: '0', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, +); + +export { SearchHeaderWrapper, RightSection, IconButton }; diff --git a/src/components/Page/Stock/ItemTitle.tsx b/src/components/Page/Stock/ItemTitle.tsx new file mode 100644 index 00000000..f71f35c1 --- /dev/null +++ b/src/components/Page/Stock/ItemTitle.tsx @@ -0,0 +1,83 @@ +import styled from '@emotion/styled'; +import { STOCK_UPDATE_TIME } from '@ts/Constants'; +import { StockCountryKey } from '@ts/StockCountry'; +import { theme } from '@styles/themes'; +import HelpSVG from '@assets/icons/question_mark_circle_fill.svg?react'; + +const ItemTextContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '4px', + padding: '0px 20px', +}); + +const ItemTitleContainer = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '6px', + + ['>p']: { + margin: '0px', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + + ['&.title']: { + ...theme.font.title20Semibold, + color: theme.colors.sub_gray2, + flexShrink: '0', + }, + + ['&.update-time']: { + ...theme.font.body14Regular, + color: theme.colors.sub_gray8, + marginLeft: 'auto', + }, + }, +}); + +const ItemHelpContainer = styled.div({ + display: 'flex', + alignItems: 'center', + gap: '4px', + + ['>p']: { + ...theme.font.detail12Medium, + color: theme.colors.sub_gray6, + margin: '0', + }, + + ['>svg']: { + width: '14px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray7, + flexShrink: '0', + }, +}); + +interface HelpProps { + text: string; + onClick: () => void; +} + +const StockItemTitle = ({ title, country, help }: { title: string; country: StockCountryKey; help?: HelpProps }) => { + const updateTime = STOCK_UPDATE_TIME[country]; + + return ( + + +

    {title}

    +

    어제 {updateTime} 기준

    +
    + {help && ( + +

    {help.text}

    + +
    + )} +
    + ); +}; + +export default StockItemTitle; diff --git a/src/components/Page/Stock/Relevant/Relevant.tsx b/src/components/Page/Stock/Relevant/Relevant.tsx new file mode 100644 index 00000000..538fe6db --- /dev/null +++ b/src/components/Page/Stock/Relevant/Relevant.tsx @@ -0,0 +1,30 @@ +import { StockCountryKey } from '@ts/StockCountry'; +import { SmallStockCard } from '@components/CardList/StockCard/StockCard'; +import { StockCardContainer, StockCardItem } from '@components/CardList/StockCard/StockCard.Style'; +import { useRelevantStockFetchQuery } from '@controllers/stocks/query'; +import { StockInfo } from '@controllers/stocks/types'; +import { StockItemContainer } from '../Common.Style'; +import StockItemTitle from '../ItemTitle'; + +const StockRelevant = ({ stockId, country }: { stockId: number; country: StockCountryKey }) => { + const { data: stocks } = useRelevantStockFetchQuery(stockId); + + if (!stocks) return null; + + return ( + + + +
    + {stocks?.map((e: StockInfo) => ( + + + + ))} +
    +
    +
    + ); +}; + +export default StockRelevant; diff --git a/src/components/Page/Stock/StockTitle/StockTitle.Style.ts b/src/components/Page/Stock/StockTitle/StockTitle.Style.ts new file mode 100644 index 00000000..44ffe1d6 --- /dev/null +++ b/src/components/Page/Stock/StockTitle/StockTitle.Style.ts @@ -0,0 +1,74 @@ +import styled from '@emotion/styled'; +import { deltaToColor } from '@utils/ScoreConvert'; +import { theme } from '@styles/themes'; + +const TitleContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '12px', + boxSizing: 'border-box', + padding: '0px 20px', +}); + +const TitleHeaderContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + + ['p']: { + margin: '0', + + ['&.name']: { + ...theme.font.heading24Semibold, + color: theme.colors.sub_white, + }, + + ['&.price']: { + ...theme.font.heading24Semibold, + color: theme.colors.sub_white, + }, + }, +}); + +const TitleDetailContainer = styled.div( + ({ delta }: { delta: number }) => ({ + ['p.price-diff']: { + color: deltaToColor(delta) ?? theme.colors.sub_gray7, + }, + }), + { + display: 'flex', + alignItems: 'center', + gap: '8px', + + ['p']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray4, + margin: '0', + }, + + ['>span']: { + display: 'flex', + alignItems: 'center', + gap: '4px', + + ['>img']: { + height: '13px', + }, + }, + + ['& > *:not(:last-child)']: { + display: 'flex', + alignItems: 'center', + gap: '8px', + ['::after']: { + content: '""', + display: 'block', + width: '1px', + height: '12px', + background: theme.colors.sub_gray6, + }, + }, + }, +); + +export { TitleContainer, TitleHeaderContainer, TitleDetailContainer }; diff --git a/src/components/Page/Stock/StockTitle/StockTitle.tsx b/src/components/Page/Stock/StockTitle/StockTitle.tsx new file mode 100644 index 00000000..3b0ec570 --- /dev/null +++ b/src/components/Page/Stock/StockTitle/StockTitle.tsx @@ -0,0 +1,58 @@ +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { MARKET_CODES } from '@ts/Constants'; +import { STOCK_COUNTRY_MAP } from '@ts/StockCountry'; +import { getDiffText, getPriceText } from '@utils/Number'; +import useAuthInfo from '@hooks/useAuthInfo'; +import { webPath } from '@router/index'; +import Button from '@components/Common/Button'; +import Marquee from '@components/Common/Marquee/Marquee'; +import useMockPurchase from '@components/Modal/MockPurchase/useMockPurchase'; +import { useBuyExperimentMutation } from '@controllers/experiment/query'; +import { StockDetailInfo } from '@controllers/stocks/types'; +import { TitleContainer, TitleDetailContainer, TitleHeaderContainer } from './StockTitle.Style'; + +const StockTitle = ({ stockInfo }: { stockInfo: StockDetailInfo }) => { + const { country, stockId, exchangeNum, priceDiff, priceDiffPerCent, price, symbol, symbolName } = stockInfo; + const navigate = useNavigate(); + const { isLogin } = useAuthInfo(); + const { mutate: buyExperiment } = useBuyExperimentMutation(); + + const { Modal: MockPurchaseModal, openModal: openMockPurchaseModal } = useMockPurchase(); + + const handleClickBuy = useCallback(() => { + if (!isLogin) { + navigate(webPath.labStep, { state: { step: 0 } }); + return; + } + + buyExperiment({ stockId, country }); + openMockPurchaseModal(); + }, [isLogin, navigate, buyExperiment, stockId, country, openMockPurchaseModal]); + + const marketCode = MARKET_CODES[exchangeNum]; + const countryImage = STOCK_COUNTRY_MAP[country].img; + + return ( + + {MockPurchaseModal} + + +

    {symbolName}

    +
    +

    {getPriceText(country, price)}

    +
    + +

    {marketCode}

    + +

    {symbol}

    + +
    +

    {getDiffText({ valueDiff: priceDiff, percentDiff: priceDiffPerCent })}

    +
    + +
    + ); +}; + +export default StockTitle; diff --git a/src/components/Page/Stock/Tab/Chart/Chart.tsx b/src/components/Page/Stock/Tab/Chart/Chart.tsx new file mode 100644 index 00000000..b3a5e6fc --- /dev/null +++ b/src/components/Page/Stock/Tab/Chart/Chart.tsx @@ -0,0 +1,178 @@ +import styled from '@emotion/styled'; +import { Fragment, useCallback, useMemo, useState } from 'react'; +import { StockCountryKey } from '@ts/StockCountry'; +import { formatMMDD } from '@utils/Date'; +import { getDiffText, getPriceText } from '@utils/Number'; +import { deltaToColor } from '@utils/ScoreConvert'; +import StockChart from '@components/Search/StockChart/StockChart'; +import { useStockChartQuery } from '@controllers/stocks/query'; +import { StockDetailInfo } from '@controllers/stocks/types'; +import { theme } from '@styles/themes'; +import { StockItemContainer } from '../../Common.Style'; +import StockItemTitle from '../../ItemTitle'; + +const ChartContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '16px', + padding: '0px 20px', +}); + +const ChartTableContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '6px', + + ['>button']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + backgroundColor: theme.colors.sub_gray11, + border: 'none', + borderRadius: '8px', + cursor: 'pointer', + padding: '10px 0', + textAlign: 'center', + }, +}); + +const ChartTableGridContainer = styled.div({ + display: 'grid', + gridTemplateColumns: 'repeat(3, 1fr)', + + ['>p']: { + margin: '0', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: '2px', + + whiteSpace: 'nowrap', + + ['&.header-item']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + marginBottom: '6px', + }, + + ['&.item']: { + padding: '9px 0', + ...theme.font.body14Bold, + color: theme.colors.sub_white, + + ['&.date']: { + ...theme.font.body14Medium, + }, + }, + }, + + ['>span.divider']: { + gridColumn: '1 / -1', + marginBottom: '6px', + height: '1px', + backgroundColor: theme.colors.sub_gray10, + }, +}); + +const ChartTableGridDeltaText = styled.span( + ({ delta }: { delta: number }) => ({ + color: deltaToColor(delta) ?? theme.colors.sub_gray6, + }), + { + ...theme.font.detail12Medium, + }, +); + +type TableRow = { + key: string; + dateText: string; + score: number | null; + scoreDiff: number | null; + price: number; + priceDiffPerCent: number; +}; + +const MORE_VIEW_COUNT = 5; + +const ChartTable = ({ stockId, country }: { stockId: number; country: StockCountryKey }) => { + const [chartData] = useStockChartQuery(stockId, 'D'); + const [viewCount, setViewCount] = useState(MORE_VIEW_COUNT); + + const handleViewMore = useCallback(() => { + setViewCount((prev) => prev + MORE_VIEW_COUNT); + }, []); + + const tableData: TableRow[] = useMemo(() => { + if (!chartData?.length) return []; + + const recent = chartData.slice(-viewCount).reverse(); + + return recent.map((e: any, idx: number) => { + const key = `CHART_TABLE_ITEM_${stockId}_${e.date instanceof Date ? e.date.getTime() : String(e.date)}_${idx}`; + + const dateObj: Date = e.date; + const scoreValue = e.score?.value ?? null; + const scoreDelta = e.score?.delta ?? null; + + return { + key, + dateText: formatMMDD(dateObj), + price: e.price.close.value, + priceDiffPerCent: e.price.close.delta * 100, + score: scoreValue, + scoreDiff: scoreDelta, + }; + }); + }, [chartData, viewCount, stockId]); + + return ( + + +

    날짜

    +

    인간지표 점수

    +

    주식가격

    + + {tableData.map((row, idx) => ( + + {idx !== 0 && } + +

    {row.dateText}

    + + {row.score == null ? ( +

    -

    + ) : ( +

    + {row.score}점 + + ({getDiffText({ valueDiff: row.scoreDiff ?? 0 })}점) + +

    + )} + +

    + {getPriceText(country, row.price)} + + ({getDiffText({ percentDiff: row.priceDiffPerCent, option: { percentFixed: 0 } })}) + +

    +
    + ))} +
    + + +
    + ); +}; + +const StockChartPanel = ({ stockInfo }: { stockInfo: StockDetailInfo }) => { + return ( + + + + + + + + ); +}; + +export default StockChartPanel; diff --git a/src/components/Page/Stock/Tab/Info/Info.tsx b/src/components/Page/Stock/Tab/Info/Info.tsx new file mode 100644 index 00000000..7a75cbdc --- /dev/null +++ b/src/components/Page/Stock/Tab/Info/Info.tsx @@ -0,0 +1,50 @@ +import styled from '@emotion/styled'; +import GuideBanner from '@components/Common/GuideBanner/GuideBanner'; +import { useStockSummaryQuery } from '@controllers/stocks/query'; +import { StockDetailInfo } from '@controllers/stocks/types'; +import { theme } from '@styles/themes'; +import { StockItemContainer } from '../../Common.Style'; +import StockItemTitle from '../../ItemTitle'; + +const InfoContainer = styled.div({ + padding: '0px 20px', + display: 'flex', + flexDirection: 'column', + gap: '16px', +}); + +const InfoTextGroup = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '4px', + minHeight: '160px', + + ['>p']: { + margin: '0', + ...theme.font.body14Medium, + color: theme.colors.sub_gray4, + textIndent: '1em', + }, +}); + +const StockInfoPanel = ({ stockInfo }: { stockInfo: StockDetailInfo }) => { + const { data: summary = [], isLoading } = useStockSummaryQuery(stockInfo.symbol, stockInfo.country); + + if (isLoading) return null; + + return ( + + + + + {summary.map((text, index) => ( +

    {text}

    + ))} +
    + +
    +
    + ); +}; + +export default StockInfoPanel; diff --git a/src/components/Page/Stock/Tab/InfoTab.tsx b/src/components/Page/Stock/Tab/InfoTab.tsx new file mode 100644 index 00000000..605b3d65 --- /dev/null +++ b/src/components/Page/Stock/Tab/InfoTab.tsx @@ -0,0 +1,86 @@ +import styled from '@emotion/styled'; +import { useCallback, useState } from 'react'; +import { StockDetailInfo } from '@controllers/stocks/types'; +import { theme } from '@styles/themes'; +import StockChartPanel from './Chart/Chart'; +import StockInfoPanel from './Info/Info'; +import StockKeywordPanel from './Keyword/Keyword'; +import StockZipyoPanel from './Zipyo/Zipyo'; + +const StockInfoTabContainer = styled.div({ display: 'flex', flexDirection: 'column', gap: '36px' }); + +const StockInfoTabHeader = styled.div<{ activeIndex: number; count: number }>( + ({ activeIndex, count }) => ({ + ['::after']: { transform: `translateX(calc(${activeIndex} * 100%))`, width: `calc((100% - 40px) / ${count})` }, + }), + { + position: 'relative', + display: 'flex', + padding: '0 20px', + borderBottom: `1px solid ${theme.colors.sub_gray10}`, + + ['::after']: { + content: '""', + position: 'absolute', + bottom: 0, + left: '20px', + height: '2px', + backgroundColor: theme.colors.sub_gray4, + transition: 'transform 0.1s ease-in-out', + }, + }, +); + +const StockInfoTabLabel = styled.label({ + cursor: 'pointer', + padding: '12px 0', + marginTop: '-12px', + position: 'relative', + flex: 1, + textAlign: 'center', + + ['input']: { display: 'none' }, + + ['>p']: { ...theme.font.body16Semibold, color: theme.colors.sub_gray7, margin: '0', transition: 'color 0.2s' }, + + ['&:hover >p']: { color: theme.colors.sub_gray2 }, + + ['>input[type="radio"]:checked ~p']: { color: theme.colors.sub_gray1 }, +}); + +type TabKey = 'HUMAN_INDEX' | 'STOCK_CHART' | 'KEYWORD' | 'COMPANY_INFO'; + +const STOCK_INFO_TABS: { key: TabKey; text: string; Panel: React.FC<{ stockInfo: StockDetailInfo }> }[] = [ + { key: 'HUMAN_INDEX', text: '인간지표', Panel: StockZipyoPanel }, + { key: 'STOCK_CHART', text: '주식차트', Panel: StockChartPanel }, + { key: 'KEYWORD', text: '키워드', Panel: StockKeywordPanel }, + { key: 'COMPANY_INFO', text: '종목정보', Panel: StockInfoPanel }, +]; + +const StockInfoTab = ({ stockInfo }: { stockInfo: StockDetailInfo }) => { + const [activeIndex, setActiveIndex] = useState(0); + + const handleTabChange = useCallback((e: React.ChangeEvent) => { + const nextIndex = Number(e.target.dataset.index); + if (Number.isNaN(nextIndex)) return; + setActiveIndex(nextIndex); + }, []); + + const ActiveTabPanel = STOCK_INFO_TABS[activeIndex].Panel; + + return ( + + + {STOCK_INFO_TABS.map(({ key, text }, idx) => ( + + +

    {text}

    +
    + ))} +
    + +
    + ); +}; + +export default StockInfoTab; diff --git a/src/components/Page/Stock/Tab/Keyword/Keyword.tsx b/src/components/Page/Stock/Tab/Keyword/Keyword.tsx new file mode 100644 index 00000000..f681ff38 --- /dev/null +++ b/src/components/Page/Stock/Tab/Keyword/Keyword.tsx @@ -0,0 +1,34 @@ +import styled from '@emotion/styled'; +import useAboutAntVoice from '@components/Modal/CenterTutorial/AboutAntVoice/useAboutAntVoice'; +import StockWordCloud from '@components/Search/StockWordCloud/StockWordCloud'; +import { StockDetailInfo } from '@controllers/stocks/types'; +import { StockItemContainer } from '../../Common.Style'; +import StockItemTitle from '../../ItemTitle'; + +const WordCloudContainer = styled.div({ + padding: '0px 20px', +}); + +const StockKeywordPanel = ({ stockInfo }: { stockInfo: StockDetailInfo }) => { + const { Modal: AboutAntVoiceModal, openModal } = useAboutAntVoice(); + + const handleOpenAntVoiceModal = () => { + openModal(); + }; + + return ( + + {AboutAntVoiceModal} + handleOpenAntVoiceModal() }} + /> + + + + + ); +}; + +export default StockKeywordPanel; diff --git a/src/components/Page/Stock/Tab/Zipyo/Zipyo.Style.ts b/src/components/Page/Stock/Tab/Zipyo/Zipyo.Style.ts new file mode 100644 index 00000000..7330d186 --- /dev/null +++ b/src/components/Page/Stock/Tab/Zipyo/Zipyo.Style.ts @@ -0,0 +1,79 @@ +import styled from '@emotion/styled'; +import { deltaToColor } from '@utils/ScoreConvert'; +import { theme } from '@styles/themes'; + +const ZipyoContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '16px', +}); + +const ZipyoDescContainer = styled.div({ + padding: '0 20px', + + ['>p']: { + ...theme.font.body14Medium, + color: theme.colors.sub_gray4, + margin: '0', + + ['>b']: { + ...theme.font.body14Semibold, + color: theme.colors.sub_gray1, + }, + }, +}); + +const ZipyoGuageChartContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '12px', + marginTop: '-12px', + + ['>p']: { + ...theme.font.detail12Semibold, + color: theme.colors.sub_gray8, + padding: '0 20px', + margin: '0', + textAlign: 'center', + }, +}); + +const ZipyoSentimentContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '10px', + margin: '0 20px', + padding: '12px 16px', + background: theme.colors.sub_gray11, + border: `1px solid ${theme.colors.sub_gray10}`, + borderRadius: '5px', + backdropFilter: 'blur(2.5px)', + + ['>p.title']: { + ...theme.font.body16Medium, + color: theme.colors.sub_gray6, + margin: '0', + }, +}); + +const ZipyoSentimentDesc = styled.span( + ({ delta }: { delta: number }) => ({ + ['>svg']: { + fill: deltaToColor(delta) ?? theme.colors.sub_gray4, + }, + }), + { + ...theme.font.body14Medium, + color: theme.colors.sub_gray6, + margin: '0', + + ['>svg']: { + width: '10px', + height: 'auto', + aspectRatio: '1 / 1', + marginRight: '4px', + }, + }, +); + +export { ZipyoContainer, ZipyoDescContainer, ZipyoGuageChartContainer, ZipyoSentimentContainer, ZipyoSentimentDesc }; diff --git a/src/components/Page/Stock/Tab/Zipyo/Zipyo.tsx b/src/components/Page/Stock/Tab/Zipyo/Zipyo.tsx new file mode 100644 index 00000000..d8a1a211 --- /dev/null +++ b/src/components/Page/Stock/Tab/Zipyo/Zipyo.tsx @@ -0,0 +1,62 @@ +import { getDiffText } from '@utils/Number'; +import { deltaToCaret } from '@utils/ScoreConvert'; +import useAboutHumanZipyo from '@components/Modal/CenterTutorial/AboutHumanZipyo/useAboutHumanZipyo'; +import GuageChart from '@components/Search/GuageChart/GuageChart'; +import { useScoreQuery, useStockZipyoDataQuery } from '@controllers/stocks/query'; +import { StockDetailInfo } from '@controllers/stocks/types'; +import { StockItemContainer } from '../../Common.Style'; +import StockItemTitle from '../../ItemTitle'; +import { + ZipyoContainer, + ZipyoDescContainer, + ZipyoGuageChartContainer, + ZipyoSentimentContainer, + ZipyoSentimentDesc, +} from './Zipyo.Style'; + +const StockZipyoPanel = ({ stockInfo: { stockId, country, symbolName } }: { stockInfo: StockDetailInfo }) => { + const { data: stockScore } = useScoreQuery(stockId, country); + const { data: zipyoData } = useStockZipyoDataQuery(stockId, country); + const { Modal: AboutHumanZipyoModal, openModal: openAboutHumanZipyoModal } = useAboutHumanZipyo(); + + if (!stockScore || !zipyoData) return null; + + const { industryName, industryAverage, stockRanking, monthlyAverage } = zipyoData; + const monthlyAverageDiff = stockScore.score - monthlyAverage; + const monthlyAverageDiffText = getDiffText({ valueDiff: monthlyAverageDiff }); + const sentiment = !monthlyAverageDiff ? '유지되고' : monthlyAverageDiff > 0 ? '개선되고' : '약화되고'; + const Caret = deltaToCaret(monthlyAverageDiff); + + return ( + + {AboutHumanZipyoModal} + openAboutHumanZipyoModal() }} + /> + + +

    + ({industryName}) 산업의 평균은 {industryAverage}점 이며, +
    ({symbolName})는 상위 {stockRanking}% 입니다. +

    +
    + + +

    ※ 인간지표는 공식 지표가 아니므로 참고 용도로만 활용해 주세요.

    +
    + +

    종목 분위기

    + + 최근 한달 평균 대비 {monthlyAverageDiffText}점 +
    해당 종목의 한 달 간의 평균 값은 ({monthlyAverage})점 입니다. +
    ({symbolName})에 대한 투자자들의 심리가 {sentiment} 있어요. +
    +
    +
    +
    + ); +}; + +export default StockZipyoPanel; diff --git a/src/components/PopUp/AntiVoicePopUp/AntVoicePopUp.style.ts b/src/components/PopUp/AntiVoicePopUp/AntVoicePopUp.style.ts deleted file mode 100644 index 1403f4cd..00000000 --- a/src/components/PopUp/AntiVoicePopUp/AntVoicePopUp.style.ts +++ /dev/null @@ -1,115 +0,0 @@ -import styled from '@emotion/styled'; -import { Globals } from '@components/Common/Common.Type'; -import { media, theme, themeColor } from '@styles/themes'; - -const PopUpImage = styled('ul')({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - padding: 0, - listStyle: 'none', - - ['div']: { - display: 'flex', - flexDirection: 'column', - alignItems: 'left', - justifyContent: 'space-between', - gap: '8px', - flex: 1, - height: '160px', - }, - [media[0]]: { - ['div']: { - height: '120px', - }, - }, -}); - -const PopUpDetailWord = styled.p( - ({ - color, - fontSize, - textAlign, - }: { - color?: themeColor; - fontSize?: number; - textAlign?: - | Globals - | '-webkit-match-parent' - | 'center' - | 'end' - | 'justify' - | 'left' - | 'match-parent' - | 'right' - | 'start'; - }) => ({ - textAlign: textAlign || 'left', - fontWeight: '700', - fontSize: fontSize ? `${fontSize}px` : '36px', - margin: 0, - padding: 0, - background: theme.colors.grayscale90, - color: color ? theme.colors[color] : theme.colors.primary0, // 색상이 없는 경우 기본값 사용 - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '100%', - width: '100%', - lineHeight: 1.3, - [media[0]]: { - fontSize: fontSize ? `${(fontSize * 2) / 3}px` : '24px', - }, - }), -); - -const PopUpDetailContainer = styled('div')({ - display: 'flex', - flexDirection: 'column', - gap: '12px', - background: theme.colors.grayscale10, - borderRadius: '8px', - padding: '16px', - marginTop: '12px', - - [media[0]]: { - padding: '12px', - marginTop: '8px', - }, -}); - -const PopUpDetail = styled('div')({ - display: 'flex', - alignItems: 'center', - gap: '12px', - - ['span']: { - fontSize: '14px', - color: theme.colors.grayscale100, - }, - - [media[0]]: { - gap: 'px', - }, -}); - -const PopUpDetailNumber = styled.div(({ color }: { color?: themeColor }) => ({ - width: '24px', - height: '24px', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - fontSize: '14px', - fontWeight: '700', - borderRadius: '4px', - background: color ? theme.colors[color] : theme.colors.primary40, - color: theme.colors.primary0, - - [media[0]]: { - width: '20px', // 모바일 크기 축소 - height: '20px', - fontSize: '12px', - }, -})); - -export { PopUpImage, PopUpDetailWord, PopUpDetailContainer, PopUpDetail, PopUpDetailNumber }; diff --git a/src/components/PopUp/AntiVoicePopUp/AntVoicePopUp.tsx b/src/components/PopUp/AntiVoicePopUp/AntVoicePopUp.tsx deleted file mode 100644 index d6b8a62e..00000000 --- a/src/components/PopUp/AntiVoicePopUp/AntVoicePopUp.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import CommonPopUp from '../CommonPopUp'; -import { PopUpContent, PopUpTitle } from '../CommonPopUp.style'; -import { - PopUpDetail, - PopUpDetailContainer, - PopUpDetailNumber, - PopUpDetailWord, - PopUpImage, -} from './AntVoicePopUp.style'; - -const AntiVoicePopUp = ({ onClose }: { onClose: () => void }) => ( - - 개미들의 목소리란? - - 각종 커뮤니티의 댓글을 한눈에 볼 수 있는 워드클라우드에요. - -
    - 1 - - 가장 많이 -
    언급된 단어 -
    -
    - -
    - 2 - - 상대적으로 적게 -
    언급된 단어 -
    -
    -
    - - - 1 - - 크기가 클수록 각종 커뮤니티에서 -
    가장 많이 언급된 단어예요. -
    -
    - - 2 - - 크기가 작을수록 각종 커뮤니티에서 -
    상대적으로 적게 언급된 단어에요 -
    -
    -
    -
    -
    -); - -export default AntiVoicePopUp; diff --git a/src/components/PopUp/AppInstallPopUp/AppInstallPopUp.style.ts b/src/components/PopUp/AppInstallPopUp/AppInstallPopUp.style.ts new file mode 100644 index 00000000..2df43132 --- /dev/null +++ b/src/components/PopUp/AppInstallPopUp/AppInstallPopUp.style.ts @@ -0,0 +1,203 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const Backdrop = styled('div')({ + position: 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + background: 'rgba(0, 0, 0, 0.5)', + zIndex: 999, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +}); + +const PopupContainer = styled.div({ + position: 'relative', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: '24px 20px 16px', + gap: '28px', + width: '324px', + background: theme.colors.sub_white, + borderRadius: '20px', + zIndex: 1000, + boxSizing: 'border-box', +}); + +const PopupTextContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + padding: 0, + gap: '12px', + width: '284px', +}); + +const TitleContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + padding: 0, + gap: '2px', + width: '284px', +}); + +const SubTitle = styled.p({ + margin: 0, + width: '284px', + fontFamily: 'Pretendard', + ...theme.font.body16Medium, + color: theme.colors.sub_gray7, +}); + +const Title = styled.h2({ + margin: 0, + width: '284px', + fontFamily: 'Pretendard', + ...theme.font.title20Semibold, + color: theme.colors.sub_gray8, +}); + +const FeaturesContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: 0, + gap: '10px', + width: '284px', + borderRadius: '5px', +}); + +const FeatureItem = styled.div({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + padding: 0, + gap: '10px', + width: '284px', +}); + +const IconWrapper = styled.div({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + padding: 0, + gap: '10px', + width: '32px', + height: '32px', + background: theme.colors.sub_blue5, + borderRadius: '500px', + flexShrink: 0, + + ['svg']: { + width: '18px', + height: '18px', + fill: theme.colors.sub_gray1, + }, +}); + +const FeatureTextContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'flex-start', + padding: 0, + flex: 1, +}); + +const FeatureLabel = styled.p({ + margin: 0, + fontFamily: 'Pretendard', + ...theme.font.detail12Medium, + color: theme.colors.sub_gray7, +}); + +const FeatureDescription = styled.p({ + margin: 0, + fontFamily: 'Pretendard', + ...theme.font.body14Semibold, + color: theme.colors.sub_gray10, +}); + +const ButtonContainer = styled.div({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + padding: 0, + gap: '12px', + width: '284px', +}); + +const CloseButton = styled.button({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + padding: '8px 0px', + gap: '10px', + width: '135px', + height: '48px', + background: theme.colors.sub_gray2, + borderRadius: '500px', + border: 'none', + cursor: 'pointer', + fontFamily: 'Pretendard', + ...theme.font.body18Semibold, + textAlign: 'center', + color: theme.colors.sub_gray8, + outline: 'none', + + ['&:active']: { + transform: 'scale(0.98)', + }, +}); + +const DownloadButton = styled.button({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + padding: '8px 0px', + gap: '10px', + width: '137px', + height: '48px', + background: theme.colors.sub_blue6, + borderRadius: '500px', + border: 'none', + cursor: 'pointer', + fontFamily: 'Pretendard', + ...theme.font.body18Semibold, + textAlign: 'center', + color: theme.colors.grayscale10, + outline: 'none', + + ['&:active']: { + transform: 'scale(0.98)', + }, +}); + +export { + Backdrop, + PopupContainer, + PopupTextContainer, + TitleContainer, + SubTitle, + Title, + FeaturesContainer, + FeatureItem, + IconWrapper, + FeatureTextContainer, + FeatureLabel, + FeatureDescription, + ButtonContainer, + CloseButton, + DownloadButton, +}; diff --git a/src/components/PopUp/AppInstallPopUp/AppInstallPopUp.tsx b/src/components/PopUp/AppInstallPopUp/AppInstallPopUp.tsx new file mode 100644 index 00000000..243bb6de --- /dev/null +++ b/src/components/PopUp/AppInstallPopUp/AppInstallPopUp.tsx @@ -0,0 +1,112 @@ +import { useState } from 'react'; +import useLocalStorageState from '@hooks/useLocalStorageState'; +import AlarmIcon from '@assets/appDownload/appDownloadAlarm.svg?react'; +import LightningIcon from '@assets/appDownload/appDownloadLightning.svg?react'; +import PhoneIcon from '@assets/appDownload/appDownloadPhone.svg?react'; +import { + Backdrop, + ButtonContainer, + CloseButton, + DownloadButton, + FeatureDescription, + FeatureItem, + FeatureLabel, + FeatureTextContainer, + FeaturesContainer, + PopupContainer, + PopupTextContainer, + SubTitle, + Title, + TitleContainer, +} from './AppInstallPopUp.style'; + +interface AppInstallPopUpProps { + onClose?: () => void; + onDownload?: () => void; +} + +const AppInstallPopUp = ({ onClose, onDownload }: AppInstallPopUpProps) => { + const [lastShown, setLastShown] = useLocalStorageState('app_install_popup_last_shown'); + const [showPopUp, setShowPopUp] = useState( + (() => { + if (!lastShown) { + return true; + } + + const diff = new Date().getTime() - new Date(lastShown).getTime(); + // 24시간 (1일) 지났으면 다시 보여주기 + if (diff < 1000 * 60 * 60 * 24) { + return false; + } + + return true; + })(), + ); + + const handleClose = () => { + setLastShown(new Date().toISOString()); + setShowPopUp(false); + onClose?.(); + }; + + const handleDownload = () => { + setShowPopUp(false); + onDownload?.(); + // TODO: 실제 앱 다운로드 링크로 이동 + // 예: window.location.href = 'https://apps.apple.com/...' 또는 'https://play.google.com/...' + }; + + const handleBackdropClick = () => { + handleClose(); + }; + + if (!showPopUp) { + return null; + } + + return ( + + e.stopPropagation()}> + + + 앱에서 더 많은 기능을 경험해보세요! + 인간지표 앱 출시! + + + + + + + 알림 기능으로 + 시장 변화를 놓치지 않고! + + + + + + + 더 쉽고 빠르게 + 지표를 확인하고! + + + + + + + 앱 전용 기능까지 + 숏뷰기능으로 더 유용하게! + + + + + + + 닫기 + 앱 다운받기 + + + + ); +}; + +export default AppInstallPopUp; diff --git a/src/components/PopUp/CommonPopUp.style.ts b/src/components/PopUp/CommonPopUp.style.ts index 92649e58..0fe93b70 100644 --- a/src/components/PopUp/CommonPopUp.style.ts +++ b/src/components/PopUp/CommonPopUp.style.ts @@ -2,57 +2,30 @@ import styled from '@emotion/styled'; import { media, theme } from '@styles/themes'; const PopUpContainer = styled('div')({ + position: 'relative', display: 'flex', flexDirection: 'column', - position: 'fixed', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: '568px', + width: '100%', height: 'auto', background: theme.colors.grayscale30, color: theme.colors.primary100, - borderRadius: '12px', + borderRadius: '8px', boxShadow: '0 4px 10px rgba(0, 0, 0, 0.2)', - zIndex: 1000, fontFamily: 'Pretendard', + overflow: 'hidden', [media[0]]: { - width: '90%', ['svg']: { width: '40%', }, }, }); -const PopUpTitle = styled('div')({ - display: 'flex', - alignItems: 'center', - gap: '8px', - fontSize: '36px', - fontWeight: '700', - padding: '48px 32px 0 32px', - - ['svg']: { - height: '36px', - width: 'auto', - }, - - [media[0]]: { - fontSize: '20px', // 모바일에서는 작은 글자 크기 - padding: '32px 32px 0 32px', - ['svg']: { - height: '18px', - width: 'auto', - }, - }, -}); - const PopUpContent = styled('div')({ - padding: '0 32px 32px 32px', + padding: '20px 16px', display: 'flex', flexDirection: 'column', - gap: '16px', + gap: '10px', fontFamily: 'Pretendard', fontSize: '16px', fontStyle: 'normal', @@ -62,26 +35,32 @@ const PopUpContent = styled('div')({ color: theme.colors.grayscale100, }); -const StyledSpan = styled('span')({ - color: theme.colors.primary50, - fontWeight: '700', +const PopUpTitle = styled('div')({ + display: 'flex', + alignItems: 'center', + gap: '4px', + + ...theme.font.body18Semibold, + color: theme.colors.primary100, + + ['>svg']: { + width: '72px', + height: 'auto', + }, }); const ConfirmButton = styled('div')({ textAlign: 'center', - fontWeight: '700', - lineHeight: '1.5', - fontSize: '24px', cursor: 'pointer', - background: theme.colors.primary50, + background: theme.colors.sub_blue6, color: theme.colors.primary0, - borderRadius: '0 0 12px 12px', - padding: '27px 0', + padding: '12px 0', + ...theme.font.body18Semibold, +}); - [media[0]]: { - fontSize: '16px', // 모바일 글자 크기 축소 - padding: '16px 0', // 모바일 패딩 축소 - }, +const StyledSpan = styled('span')({ + color: theme.colors.primary50, + fontWeight: '700', }); const Backdrop = styled('div')({ @@ -105,8 +84,8 @@ const CloseButton = styled('button')({ color: theme.colors.grayscale100, [media[0]]: { - top: '12px', - right: '0px', + top: '8px', + right: '8px', }, }); diff --git a/src/components/PopUp/CommonPopUp.tsx b/src/components/PopUp/CommonPopUp.tsx index 25faad7b..e990730b 100644 --- a/src/components/PopUp/CommonPopUp.tsx +++ b/src/components/PopUp/CommonPopUp.tsx @@ -1,14 +1,10 @@ -import { Backdrop, CloseButton, ConfirmButton, PopUpContainer } from './CommonPopUp.style'; +import { ConfirmButton, PopUpContainer, PopUpContent } from './CommonPopUp.style'; const CommonPopUp = ({ children, onClose }: { children: any; onClose: () => void }) => ( - <> - - - - {children} - 이해했어요 - - + + {children} + 이해했어요 + ); export default CommonPopUp; diff --git a/src/components/PopUp/DescentPopUp/DescentPopUp.tsx b/src/components/PopUp/DescentPopUp/DescentPopUp.tsx deleted file mode 100644 index 9d6875a5..00000000 --- a/src/components/PopUp/DescentPopUp/DescentPopUp.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import CommonPopUp from '../CommonPopUp'; -import { PopUpContent, PopUpTitle, StyledSpan } from '../CommonPopUp.style'; - -const ZipyoPopUp = ({ onClose }: { onClose: () => void }) => ( - - 지금 민심 떡락중인 지표는? - -
    - 현재 개미들의 민심이 부정적으로 급하락중인 - 종목입니다. 다양한 커뮤니티 데이터를 분석해 인간지표 점수를 계산했습니다. -
    -
    -
    -); - -export default ZipyoPopUp; diff --git a/src/components/PopUp/FearPopUp/FearPopUp.tsx b/src/components/PopUp/FearPopUp/FearPopUp.tsx deleted file mode 100644 index be9d3701..00000000 --- a/src/components/PopUp/FearPopUp/FearPopUp.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import CommonPopUp from '../CommonPopUp'; -import { PopUpContent, PopUpTitle, StyledSpan } from '../CommonPopUp.style'; - -const FearPopUp = ({ onClose }: { onClose: () => void }) => ( - - 공포탐욕지수란? - -
    - 공포탐욕지수는 시장의 7가지 요인을 분석하여 - 현재 투자자들의 심리를 극단적인 공포(0)부터 극단적인 탐욕(100)에 이르기까지를 가늠하는 - 심리지표입니다. -
    -
    -
    -); - -export default FearPopUp; diff --git a/src/components/PopUp/HotPopUp/HotPopUp.tsx b/src/components/PopUp/HotPopUp/HotPopUp.tsx deleted file mode 100644 index 4bb59aab..00000000 --- a/src/components/PopUp/HotPopUp/HotPopUp.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import CommonPopUp from '../CommonPopUp'; -import { PopUpContent, PopUpTitle, StyledSpan } from '../CommonPopUp.style'; - -const HotPopUp = ({ onClose }: { onClose: () => void }) => ( - - 지금 가장 HOT한 지표는? - -
    - 현재 투자자들이 가장 주목하는 지표입니다. 인간지표의 검색어 순위를 기반으로 계산됩니다. -
    -
    -
    -); - -export default HotPopUp; diff --git a/src/components/PopUp/KeywordPopUp/KeywordPopUp.tsx b/src/components/PopUp/KeywordPopUp/KeywordPopUp.tsx deleted file mode 100644 index c92d3bf8..00000000 --- a/src/components/PopUp/KeywordPopUp/KeywordPopUp.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import CommonPopUp from '../CommonPopUp'; -import { PopUpContent, PopUpTitle, StyledSpan } from '../CommonPopUp.style'; - -const KeywordPopUp = ({ onClose }: { onClose: () => void }) => ( - - 가장 많이 언급되고 있는 키워드란? - -
    - 인간지표는 다양한 커뮤니티에서 수집한 데이터 기반의 워드클라우드(개미들의 목소리)를 제공합니다. - 현재 개미들이 가장 많이 언급하는 단어를 한눈에 확인할 수 있습니다. -
    -
    -
    -); - -export default KeywordPopUp; diff --git a/src/components/PopUp/PWAinfoPopUp/PWAInfoPopUp.tsx b/src/components/PopUp/PWAinfoPopUp/PWAInfoPopUp.tsx index 391bda51..26137bc5 100644 --- a/src/components/PopUp/PWAinfoPopUp/PWAInfoPopUp.tsx +++ b/src/components/PopUp/PWAinfoPopUp/PWAInfoPopUp.tsx @@ -1,51 +1,38 @@ -import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useState } from 'react'; import { useIsMobile } from '@hooks/useIsMobile'; -import { webPath } from '@router/index'; -import { ImgDiv } from '@components/Common/Common'; +import useLocalStorageState from '@hooks/useLocalStorageState'; +import useRouter from '@router/useRouter'; import PWAPNG from '@assets/PWA/PWA.png'; +import CrossSVG from '@assets/icons/cross.svg?react'; import { Backdrop, - ButtonContainer, - Close24HourButton, - CloseButton, - ConfirmButton, - DetailContainer, - HeaderText, - NormalText, + PWAInfoButtonContainer, PWAInfoContainer, - TextArea, + PWAInfoContents, + PWAInfoTextContainer, } from './PWAinfoPopUp.style'; -const PWAInfoPopUp = ({}: {}) => { - const [showPopUp, setShowPopUp] = useState(false); +const PWAInfoPopUp = () => { + const { navToUsage } = useRouter(); const isMobile = useIsMobile(); - const navigate = useNavigate(); - - useEffect(() => { - const today = new Date(); - const VISITED = localStorage.getItem('LAST_VISIT_POPUP'); // 마지막 방문 시간을 로컬 스토리지에서 가져옴 - - const handleMainPop = () => { - if (VISITED) { - const lastVisit = new Date(VISITED); - const diff = today.getTime() - lastVisit.getTime(); - const diffHours = diff / (1000 * 60 * 60); - - if (diffHours < 24) { - return; - } + const [lastVisit, setLastVisit] = useLocalStorageState('last_visit_page'); + const [showPopUp, setShowPopUp] = useState( + (() => { + if (!lastVisit) { + return true; } - setShowPopUp(true); - }; + const diff = new Date().getTime() - new Date(lastVisit).getTime(); + if (diff < 1000 * 60 * 60 * 24) { + return false; + } - handleMainPop(); - }, []); + return true; + })(), + ); const closePopUp24Hours = () => { - const today = new Date(); - localStorage.setItem('LAST_VISIT_POPUP', today.toISOString()); + setLastVisit(new Date(new Date().getTime() - (new Date().getTime() % (1000 * 60 * 60 * 24))).toDateString()); setShowPopUp(false); }; @@ -55,7 +42,7 @@ const PWAInfoPopUp = ({}: {}) => { const confirmClick = () => { setShowPopUp(false); - navigate(webPath.usage()); + navToUsage(); }; return ( @@ -64,25 +51,28 @@ const PWAInfoPopUp = ({}: {}) => { <> - - - - - - - 24시간 동안 안보기 - 사용법 보기 - +

    + + + + + + +
    ) diff --git a/src/components/PopUp/PWAinfoPopUp/PWAinfoPopUp.style.ts b/src/components/PopUp/PWAinfoPopUp/PWAinfoPopUp.style.ts index 55c5e2ed..7c409b18 100644 --- a/src/components/PopUp/PWAinfoPopUp/PWAinfoPopUp.style.ts +++ b/src/components/PopUp/PWAinfoPopUp/PWAinfoPopUp.style.ts @@ -1,12 +1,19 @@ import styled from '@emotion/styled'; import { theme } from '@styles/themes'; +const Backdrop = styled('div')({ + position: 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + background: 'rgba(0, 0, 0, 0.5)', + zIndex: 999, +}); + const PWAInfoContainer = styled.div({ - display: 'flex', position: 'fixed', - bottom: '0', - left: '50%', - transform: 'translateX(-50%)', + bottom: '0px', borderRadius: '12px 12px 0 0', boxShadow: '0 4px 10px rgba(0, 0, 0, 0.2)', zIndex: 1000, @@ -16,78 +23,84 @@ const PWAInfoContainer = styled.div({ color: 'black', fontFamily: 'Pretendard', fontStyle: 'normal', + pointerEvents: 'auto', + padding: '32px 24px', + boxSizing: 'border-box', + gap: '24px', + display: 'flex', + + ['>svg']: { + position: 'absolute', + bottom: '100%', + right: '0px', + margin: '4px', + width: '36px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_gray5, + }, }); -const DetailContainer = styled('div')({ +const PWAInfoContents = styled.div({ display: 'flex', - flexDirection: 'row', - alignContent: 'flex-start', - alignItems: 'flex-start', + gap: '16px', + alignItems: 'center', justifyContent: 'center', - gap: '15px', - padding: '30px', -}); -const TextArea = styled('div')({ - flexDirection: 'row', - gap: '15px', + ['>img']: { + maxWidth: '150px', + width: '100%', + minWidth: '0', + }, }); -const HeaderText = styled('h2')({}); +const PWAInfoTextContainer = styled.div({ + display: 'flex', + flexDirection: 'column', + gap: '10px', + flexShrink: '0', + + ['>p']: { + margin: '0', + color: theme.colors.sub_black, + + ['&.title']: { + ...theme.font.heading24Bold, + }, -const NormalText = styled('div')({ - fontWeight: 500, + ['&.description']: { + ...theme.font.body16Medium, + }, + }, }); -const ButtonContainer = styled('div')({ +const PWAInfoButtonContainer = styled.div({ display: 'flex', - padding: '0 30px 30px 30px', justifyContent: 'center', gap: '20px', fontStyle: 'normal', -}); -const StyledButton = styled('button')({ - fontSize: '15px', - lineHeight: 1.5, - width: '160px', - borderRadius: '8px', - padding: '16px', - fontFamily: 'Pretendard', - fontWeight: 700, -}); - -const Close24HourButton = styled(StyledButton)({ - backgroundColor: theme.colors.primary0, - color: theme.colors.grayscale90, - border: `1px solid ${theme.colors.grayscale10}`, -}); - -const ConfirmButton = styled(StyledButton)({ - backgroundColor: theme.colors.primary50, - color: theme.colors.grayscale5, - border: 'none', -}); - -const Backdrop = styled('div')({ - position: 'fixed', - top: 0, - left: 0, - width: '100%', - height: '100%', - background: 'rgba(0, 0, 0, 0.5)', - zIndex: 999, -}); + ['>button']: { + width: '160px', + borderRadius: '8px', + padding: '12px 8px', + fontFamily: 'Pretendard', + ...theme.font.body14Semibold, + wordBreak: 'keep-all', + outline: 'none', + cursor: 'pointer', + border: `1px solid transparent`, -const CloseButton = styled('button')({ - position: 'absolute', - top: '-40px', - right: '-20px', - background: 'none', - border: 'none', - fontSize: '30px', - cursor: 'pointer', - color: theme.colors.primary0, + ['&.white']: { + background: theme.colors.sub_white, + color: theme.colors.sub_gray8, + borderColor: theme.colors.sub_gray2, + }, + ['&.blue']: { + background: theme.colors.sub_blue6, + color: theme.colors.sub_white, + }, + }, }); -export { PWAInfoContainer, DetailContainer, HeaderText, NormalText, TextArea, ButtonContainer, Close24HourButton, ConfirmButton, Backdrop, CloseButton }; +export { Backdrop, PWAInfoContainer, PWAInfoContents, PWAInfoTextContainer, PWAInfoButtonContainer }; diff --git a/src/components/PopUp/RisingPopUp/RisingPopUp.tsx b/src/components/PopUp/RisingPopUp/RisingPopUp.tsx deleted file mode 100644 index de5891ad..00000000 --- a/src/components/PopUp/RisingPopUp/RisingPopUp.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import CommonPopUp from '../CommonPopUp'; -import { PopUpContent, PopUpTitle, StyledSpan } from '../CommonPopUp.style'; - -const RisingPopUp = ({ onClose }: { onClose: () => void }) => ( - - 지금 민심 떡상중인 지표는? - -
    - 현재 개미들의 민심이 긍정적으로 급상승중인 종목입니다. 다양한 커뮤니티 데이터를 분석해 인간지표 점수를 계산했습니다. -
    -
    -
    -); - -export default RisingPopUp; diff --git a/src/components/PopUp/ZipyoPopUp/ZipyoPopUp.style.ts b/src/components/PopUp/ZipyoPopUp/ZipyoPopUp.style.ts deleted file mode 100644 index b5ba5cc8..00000000 --- a/src/components/PopUp/ZipyoPopUp/ZipyoPopUp.style.ts +++ /dev/null @@ -1,172 +0,0 @@ -import styled from '@emotion/styled'; -import { media, theme, themeColor } from '@styles/themes'; - -const PopUpContainer = styled('div')({ - display: 'flex', - flexDirection: 'column', - position: 'fixed', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: '568px', - height: 'auto', - background: theme.colors.grayscale30, - color: theme.colors.primary100, - borderRadius: '12px', - boxShadow: '0 4px 10px rgba(0, 0, 0, 0.2)', - zIndex: 1000, - fontFamily: 'Pretendard', - - [media[0]]: { - width: '90%', - ['svg']: { - width: '40%', - }, - }, -}); - -const PopUpImage = styled('ul')({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - gap: '8px', - padding: 0, - listStyle: 'none', - - ['div']: { - display: 'flex', - flexDirection: 'column', - alignItems: 'left', - justifyContent: 'space-between', - gap: '8px', - flex: 1, - height: '160px', - - ['p']: { - textAlign: 'center', - fontWeight: '700', - fontSize: '16px', - margin: 0, - background: theme.colors.grayscale90, - color: theme.colors.primary0, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '100%', - borderRadius: '8px', - width: '100%', - }, - - ['img']: { - height: '100%', - width: '100%', - borderRadius: '8px', - }, - }, - - [media[0]]: { - ['div']: { - height: '120px', - ['img']: { - height: '100%', - width: '100%', - borderRadius: '8px', - }, - }, - }, -}); - -const PopUpDetailContainer = styled('div')({ - display: 'flex', - flexDirection: 'column', - gap: '12px', - background: theme.colors.grayscale10, - borderRadius: '8px', - padding: '16px', - marginTop: '12px', - - [media[0]]: { - padding: '12px', // 모바일 내부 여백 축소 - marginTop: '8px', - }, -}); - -const PopUpDetail = styled('div')({ - display: 'flex', - alignItems: 'center', - gap: '12px', - - ['span']: { - fontSize: '14px', - color: theme.colors.grayscale100, - }, - - [media[0]]: { - ['span']: { - fontSize: '9px', - }, - }, -}); - -const PopUpDetailNumber = styled.div(({ color }: { color?: themeColor }) => ({ - width: '24px', - height: '24px', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - fontSize: '14px', - fontWeight: '700', - borderRadius: '4px', - background: color ? theme.colors[color] : theme.colors.primary40, - color: theme.colors.primary0, - - [media[0]]: { - width: '20px', // 모바일 크기 축소 - height: '20px', - fontSize: '12px', - }, -})); - -const ConfirmButton = styled('div')({ - textAlign: 'center', - fontWeight: '500', - fontSize: '16px', - cursor: 'pointer', - background: theme.colors.primary50, - color: theme.colors.primary0, - borderRadius: '0 0 12px 12px', - padding: '27px 0', - - [media[0]]: { - fontSize: '14px', // 모바일 글자 크기 축소 - padding: '16px 0', // 모바일 패딩 축소 - }, -}); - -const Backdrop = styled('div')({ - position: 'fixed', - top: 0, - left: 0, - width: '100%', - height: '100%', - background: 'rgba(0, 0, 0, 0.5)', - zIndex: 999, -}); - -const CloseButton = styled('button')({ - position: 'absolute', - top: '12px', - right: '12px', - background: 'none', - border: 'none', - fontSize: '18px', - cursor: 'pointer', - color: theme.colors.grayscale100, - - [media[0]]: { - top: '12px', - right: '0px', - }, -}); - -export { PopUpContainer, PopUpImage, PopUpDetailContainer, PopUpDetail, PopUpDetailNumber, ConfirmButton, Backdrop, CloseButton }; diff --git a/src/components/PopUp/ZipyoPopUp/ZipyoPopUp.tsx b/src/components/PopUp/ZipyoPopUp/ZipyoPopUp.tsx deleted file mode 100644 index 49c47ac5..00000000 --- a/src/components/PopUp/ZipyoPopUp/ZipyoPopUp.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import LogoSVG from '@assets/logo_blue.svg?react'; -import badPNG from '@assets/stockScore/bad.png'; -import CommonPopUp from '../CommonPopUp'; -import { PopUpContent, PopUpTitle } from '../CommonPopUp.style'; -import { PopUpDetail, PopUpDetailContainer, PopUpDetailNumber, PopUpImage } from './ZipyoPopUp.style'; - -const ZipyoPopUp = ({ onClose }: { onClose: () => void }) => ( - - - - 점수란 - - - 인간지표만의 알고리즘을 사용하여 주식 관련 커뮤니티의 댓글을 분석해 민심을 점수화했어요. 점수는 하루에 한 번씩 업데이트돼요. - -
    - 1 -

    "극대노"

    -
    -
    - 2 - 민심 이미지 -
    -
    - 3 -

    14점

    -
    -
    - - - 1 - 민심 점수를 한 단어로 설명하는 키워드에요 - - - 2 - 민심 점수에 해당하는 이미지에요. - - - 3 - - 인간지표만의 알고리즘으로 도출된 종목에 대한 민심 점수에요.
    - 점수가 높을수록 현재 개미들의 민심이 좋다는 것을 의미해요. -
    -
    -
    -
    -
    -); - -export default ZipyoPopUp; diff --git a/src/components/Search/GuageChart/GuageChart.Style.ts b/src/components/Search/GuageChart/GuageChart.Style.ts new file mode 100644 index 00000000..6db06ada --- /dev/null +++ b/src/components/Search/GuageChart/GuageChart.Style.ts @@ -0,0 +1,149 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; +import BalloonMaskPNG from '@assets/mask_balloon.png'; + +const ARC_COLORS = [ + theme.colors.sub_blue9, + theme.colors.sub_blue8, + theme.colors.sub_blue7, + theme.colors.sub_blue6, + theme.colors.sub_blue5, +]; + +const GuageChartContainer = styled.div({ + height: 'auto', + width: '100%', + aspectRatio: '7 / 4', + position: 'relative', + overflow: 'hidden', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + boxSizing: 'border-box', +}); + +const GuageChartContentsInner = styled.div({ + position: 'absolute', + bottom: '0', + transform: 'translateY(50%)', + width: '100%', + height: 'auto', + aspectRatio: '1 / 1', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +}); + +const GuageChartItem = styled.div({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +}); + +const GuageChartItemArc = styled.span( + ({ index, selected }: { index: number; selected: boolean }) => ({ + width: selected ? '80%' : '75%', + opacity: selected ? 1 : 0.5, + filter: selected ? `drop-shadow(0px 4px 8px rgba(0, 0, 0, 0.5))` : 'none', + + ['::after']: { + background: `conic-gradient(from ${-90 + index * 36}deg, ${selected ? theme.colors.sub_blue6 : ARC_COLORS[index]} 0deg, ${selected ? theme.colors.sub_blue6 : ARC_COLORS[index]} 36deg, transparent 36deg), + radial-gradient(circle at center, transparent 50%, transparent 50%)`, + }, + }), + { + position: 'absolute', + height: 'auto', + aspectRatio: '1 / 1', + + ['::after']: { + content: '""', + display: 'block', + width: '100%', + height: '100%', + borderRadius: '50%', + mask: `radial-gradient(closest-side, transparent calc(40%), #000 0)`, + }, + }, +); + +const GuageChartItemText = styled.span( + ({ index, selected }: { index: number; selected: boolean }) => ({ + left: `calc(50% + sin(-90deg + ${index} * 36deg + 18deg) * ${selected ? 0.56 : 0.525} * 50%)`, + top: `calc(50% - cos(-90deg + ${index} * 36deg + 18deg) * ${selected ? 0.56 : 0.525} * 50%)`, + ...theme.font[selected ? 'body18Semibold' : 'body16Semibold'], + opacity: selected ? 1 : 0.5, + }), + { + position: 'absolute', + transform: `translate(-50%, -50%)`, + margin: '0px', + color: theme.colors.sub_white, + }, +); + +const GuageChartItemBalloon = styled.div( + ({ index }: { index: number }) => ({ + left: `calc(50% + sin(-90deg + ${index} * 36deg + 18deg) * 0.65 * 50%)`, + top: `calc(50% - cos(-90deg + ${index} * 36deg + 18deg) * 0.65 * 50%)`, + transform: `translate(-50%, calc(${index === 2 ? -100 : index === 1 || index === 3 ? -105 : -115}%))`, + }), + { + zIndex: '4', + position: 'absolute', + + width: '30%', + height: 'auto', + aspectRatio: '5 / 4', + display: 'flex', + filter: 'drop-shadow(0px 4px 16px rgba(0, 0, 0, 0.25))', + + ['>img']: { + width: '100%', + height: '100%', + objectFit: 'cover', + maskImage: `url(${BalloonMaskPNG})`, + maskSize: '100% 100%', + }, + }, +); + +const GuageChartItemScorePlaceholder = styled.p( + ({ index }: { index: number }) => ({ + left: `calc(50% + sin(-90deg + ${index} * 36deg) * 0.8 * 50%)`, + top: `calc(50% - cos(-90deg + ${index} * 36deg) * 0.8 * 50%)`, + transform: `translate(${index < 2 ? -100 : index < 4 ? -50 : 0}%, -100%)`, + }), + { + position: 'absolute', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + padding: '0px 4px', + color: theme.colors.sub_white, + ...theme.font.body16Medium, + opacity: '0.2', + margin: '0', + }, +); + +const GuageChartItemScore = styled.p({ + position: 'absolute', + bottom: '0', + left: '50%', + transform: 'translateX(-50%)', + color: theme.colors.sub_blue6, + ...theme.font.title20Semibold, + marginBottom: '4px', +}); + +export { + GuageChartContainer, + GuageChartContentsInner, + GuageChartItem, + GuageChartItemArc, + GuageChartItemText, + GuageChartItemBalloon, + GuageChartItemScorePlaceholder, + GuageChartItemScore, +}; diff --git a/src/components/Search/GuageChart/GuageChart.tsx b/src/components/Search/GuageChart/GuageChart.tsx new file mode 100644 index 00000000..8da37213 --- /dev/null +++ b/src/components/Search/GuageChart/GuageChart.tsx @@ -0,0 +1,53 @@ +import { scoreToImage, scoreToIndex } from '@utils/ScoreConvert'; +import { + GuageChartContainer, + GuageChartContentsInner, + GuageChartItem, + GuageChartItemArc, + GuageChartItemBalloon, + GuageChartItemScore, + GuageChartItemScorePlaceholder, + GuageChartItemText, +} from './GuageChart.Style'; + +const GuageChart = ({ score }: { score: number }) => { + const scoreRange = [0, 30, 40, 50, 70, 100]; + const scoreText = ['대곰탕', '곰탕', '어?', '"호황"', '대호황!']; + const scoreIndex = scoreToIndex(score); + const scoreImage = scoreToImage(score); + + return ( + + + {scoreRange.map((e, index) => { + return ( + + {e} + + ); + })} + + + {scoreText.map((e, index) => { + return ( + + + + {e} + + + {index === scoreIndex && ( + + score-image + + )} + + ); + })} + + {score}점 + + ); +}; + +export default GuageChart; diff --git a/src/components/Search/SearchTitle/SearchTitle.Style.ts b/src/components/Search/SearchTitle/SearchTitle.Style.ts deleted file mode 100644 index b10da112..00000000 --- a/src/components/Search/SearchTitle/SearchTitle.Style.ts +++ /dev/null @@ -1,194 +0,0 @@ -import styled from '@emotion/styled'; -import { motion } from 'framer-motion'; -import { media, theme, themeColor } from '@styles/themes'; - -export const SearchTitleContainer = styled.div({ - display: 'flex', - flexDirection: 'column', - gap: '32px', - boxSizing: 'border-box', - width: '100%', - maxWidth: '1280px', - margin: '0 auto', - - color: theme.colors.grayscale30, - - background: theme.colors.primary100, - - [media[0]]: { - gap: '24px', - padding: '0 20px', - }, -}); - -export const SearchTitleHeaderContainer = styled.div({ - display: 'flex', - alignItems: 'end', - justifyContent: 'space-between', -}); - -export const SearchTitleHeaderSymbol = styled.p({ - margin: '0', - padding: '8px 16px', - - fontSize: '15px', - - background: theme.colors.grayscale100, - borderRadius: '24px', - - [media[0]]: { - padding: '4px 12px', - - fontSize: '13px', - }, -}); - -export const SearchTitleHeaderButton = styled.div({ - display: 'flex', - gap: '12px', - alignItems: 'center', - padding: '12px 20px', - - fontWeight: '700', - fontSize: '17px', - lineHeight: '1', - - background: theme.colors.primary50, - borderRadius: '8px', - cursor: 'pointer', - - ['svg']: { - stroke: theme.colors.primary0, - - strokeWidth: '1.5', - }, - - [media[0]]: { - gap: '8px', - padding: '12px 16px', - - fontSize: '15px', - - ['svg']: { - width: '16px', - height: '16px', - }, - }, -}); - -export const SearchTitleBody = styled.div({ - display: 'flex', - flexDirection: 'column', - gap: '16px', - - [media[0]]: { - gap: '12px', - }, -}); - -export const SearchTitleBodyTitle = styled.div({ - display: 'flex', - alignItems: 'center', - - fontWeight: '700', - fontSize: '42px', - lineHeight: '1', - color: theme.colors.transparent, - - [media[0]]: { - fontSize: '32px', - }, -}); - -export const SearchTitleBodyTitleText = styled.div({ - position: 'relative', - - overflow: 'hidden', - boxSizing: 'content-box', - - textWrap: 'nowrap', - textOverflow: 'ellipsis', -}); - -export const SearchTitleBodyTitleAnimatedText = styled(motion.div)({ - willChange: 'transform', - position: 'absolute', - top: '0', - - color: theme.colors.primary0, -}); - -export const SearchTitleBodyTitleSVG = styled.div({ - display: 'flex', - paddingLeft: '12px', - - ['svg']: { - width: '85px', - marginRight: 'auto', - - textWrap: 'nowrap', - overflowWrap: 'anywhere', - - fill: theme.colors.primary50, - }, - - [media[0]]: { - paddingLeft: '8 px', - - ['svg']: { - width: '56px', - }, - }, -}); - -export const SearchTitleBodySubtitle = styled.div({ - display: 'flex', - flexDirection: 'column', - - fontSize: '15px', - - [media[0]]: { - fontSize: '11px', - }, -}); - -export const SearchTitleFooterContainer = styled.div({ - display: 'flex', - gap: '12px', -}); - -export const SearchTitleFooterItems = styled.div( - { - display: 'flex', - gap: '8px', - alignItems: 'center', - padding: '12px 18px', - - fontWeight: '700', - fontSize: '17px', - lineHeight: '1', - - background: theme.colors.grayscale100, - borderRadius: '8px', - - ['span']: { - fontWeight: '500', - fontSize: '15px', - }, - - [media[0]]: { - padding: '8px 12px', - - fontSize: '15px', - - ['span']: { - fontSize: '13px', - }, - }, - }, - ({ delta }: { delta?: themeColor }) => ({ - ['span']: { - color: theme.colors[delta ?? 'primary0'], - }, - }), -); diff --git a/src/components/Search/SearchTitle/SearchTitle.tsx b/src/components/Search/SearchTitle/SearchTitle.tsx deleted file mode 100644 index 92da0fa5..00000000 --- a/src/components/Search/SearchTitle/SearchTitle.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { AnimatePresence, Variants, useCycle } from 'framer-motion'; -import { useEffect, useRef, useState } from 'react'; -import { useLocation } from 'react-router-dom'; -import { MARKET_CODES, ResultInfo } from '@ts/Constants'; -import { RESULT_TYPE } from '@ts/Types'; -import { deltaColor } from '@utils/Delta'; -import { StockDetailInfo } from '@controllers/api.Type'; -import { useStockSummaryQuery } from '@controllers/query'; -import RightSVG from '@assets/icons/right.svg?react'; -import ZipyoSVG from '@assets/zipyo.svg?react'; -import { - SearchTitleBody, - SearchTitleBodySubtitle, - SearchTitleBodyTitle, - SearchTitleBodyTitleAnimatedText, - SearchTitleBodyTitleSVG, - SearchTitleBodyTitleText, - SearchTitleContainer, - SearchTitleFooterContainer, - SearchTitleFooterItems, - SearchTitleHeaderButton, - SearchTitleHeaderContainer, - SearchTitleHeaderSymbol, -} from './SearchTitle.Style'; - -const BASE_DELAY = 1500; - -const priceDiff = (diff: number) => `${(diff < 0 ? '-' : '+') + Math.abs(diff).toLocaleString()}`; - -const SearchTitle = ({ - stockInfo, - resultMode, - onClick, -}: { - stockInfo: StockDetailInfo; - resultMode: RESULT_TYPE; - onClick: (e: any) => void; -}) => { - const { state } = useLocation(); - - const money = stockInfo.country === 'KOREA' ? '₩' : '$'; - - const titleTextRef = useRef(null); - - const [animated, setAnimated] = useState(false); - const [animationDelay, setAnimationDelay] = useState({ - initial: BASE_DELAY, - animate: BASE_DELAY, - instant: BASE_DELAY, - }); - const [animation, cycleAnimation] = useCycle(...Object.keys(animationDelay)); - - const [summary] = useStockSummaryQuery(stockInfo.symbol, stockInfo.country); - - const variants: Variants = { - initial: { - transform: 'translateX(0%)', - }, - animate: { - transform: - 'translateX(' + ((titleTextRef.current?.offsetWidth ?? 0) - (titleTextRef.current?.scrollWidth ?? 0)) + 'px)', - transition: { - duration: animationDelay['animate'] / 1000, - ease: 'linear', - }, // 애니메이션 - }, - instant: { - transform: 'translateX(0%)', - transition: { - delay: animationDelay['instant'] / 1000, - duration: 0, - ease: 'linear', - }, // 즉시 이동 - }, - }; - - useEffect(() => { - if (titleTextRef.current) { - const { offsetWidth, scrollWidth } = titleTextRef.current; - setAnimated(scrollWidth > offsetWidth); - setAnimationDelay({ - ...animationDelay, - animate: BASE_DELAY * (scrollWidth / offsetWidth - 1) * 2, - }); - } - }, [state]); - - useEffect(() => { - const interval = setInterval(() => { - if (animated) cycleAnimation(); - }, animationDelay[animation]); - return () => clearInterval(interval); - }, [animation, animated]); - - return ( - stockInfo && ( - - - {stockInfo.symbol} - - {ResultInfo[ResultInfo[resultMode].opposite].text} 보기 - - - - - - - {stockInfo.symbolName} - - - {stockInfo.symbolName} - - - - - - - - - {summary.map((e, i) => ( - {e} - ))} - - - - {MARKET_CODES[stockInfo.exchangeNum]} - - {money} {stockInfo.price.toLocaleString()} - {`${priceDiff(stockInfo.priceDiff)}(${stockInfo.priceDiffPerCent}%)`} - - - - ) - ); -}; - -export default SearchTitle; diff --git a/src/components/Search/StockChart/ChartView.tsx b/src/components/Search/StockChart/ChartView.tsx new file mode 100644 index 00000000..3765ff27 --- /dev/null +++ b/src/components/Search/StockChart/ChartView.tsx @@ -0,0 +1,1126 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { OPEN_DAYS } from '@ts/OpenDays'; +import { StockCountryKey } from '@ts/StockCountry'; +import { PERIOD_CODE } from '@ts/Types'; +import { CanvasSize } from '@utils/Canvas'; +import { formatDateISO } from '@utils/Date'; +import { + DATE_UNIT_PRIORITY, + formatGridLabel, + formatYMD, + getUnitByMetaChange, + lowerBoundNumber, + parseYmdMeta, +} from '@utils/date/ymd'; +import { DateUnit, IndexedYmd, LabeledGridYmd, YMD, YmdMeta } from '@utils/date/ymd.types'; +import { getLocalPos, getTouchPoints } from '@utils/domPointer'; +import { Point, avgRadius, centroid } from '@utils/geometry2d'; +import { useIsMobile } from '@hooks/useIsMobile'; +import useStateRef from '@hooks/useStateRef'; +import StockChartDateLabel from './DateLabel'; +import StockChartExtremePrice from './ExtremePrice'; +import StockChartGridCanvas from './GridCanvas'; +import { GridItem } from './GridLabel'; +import StockChartPointerCanvas from './PointerCanvas'; +import StockPriceChart from './PriceChart'; +import StockChartPriceInfo from './PriceInfo'; +import StockChartPriceLabel from './PriceLabel'; +import StockScoreChart from './ScoreChart'; +import StockChartScoreInfo from './ScoreInfo'; +import StockChartScoreLabel from './ScoreLabel'; +import { ChartHeight, ViewCanvasContainer, ViewContainer, deltaToChartColor } from './StockChart.Style'; + +const formatPrice = (value: number, country: StockCountryKey) => { + const digit = country === 'OVERSEA' ? 2 : 0; + + return value.toLocaleString(undefined, { + minimumFractionDigits: digit, + maximumFractionDigits: digit, + }); +}; + +export const pickMetaKeyByPeriod = (period: PERIOD_CODE, meta: YmdMeta) => (period === 'M' ? meta.ymKey : meta.ymdKey); + +const AXIS_SCALE_RATIOS = [1, 2, 2.5, 4, 5]; +const AXIS_SCALE_VALUES = Array.from({ length: 12 }, (_, i) => 10 ** (i - 3)).flatMap((dec) => + AXIS_SCALE_RATIOS.map((r) => dec * r), +); + +interface ValueRange { + max: number; + min: number; +} + +const clamp = (v: number, min: number, max: number) => Math.min(Math.max(v, min), max); + +const getScaleY = (top: number, bottom: number, range: ValueRange) => + (bottom - top) / Math.max(range.max - range.min, 1); + +const valueToY = ( + value: number, + height: number, + margin: { + top: number; + bottom: number; + }, + range: ValueRange, + zoom: number = 1, +) => { + const { top, bottom } = margin; + + const plotTop = top; + const plotBottom = height - bottom; + + const centerY = (plotTop + plotBottom) / 2; + const scaleY = getScaleY(plotTop, plotBottom, range); + + const zoomY = (y0: number) => centerY + (y0 - centerY) / zoom; + return zoomY(plotBottom - (value - range.min) * scaleY); +}; + +const yToValue = ( + y: number, + height: number, + margin: { + top: number; + bottom: number; + }, + range: ValueRange, + zoom: number = 1, +) => { + const { top, bottom } = margin; + + const plotTop = top; + const plotBottom = height - bottom; + + const centerY = (plotTop + plotBottom) / 2; + const scaleY = getScaleY(plotTop, plotBottom, range); + + const unzoomY = (y: number) => centerY + (y - centerY) * zoom; + return range.min + (plotBottom - unzoomY(y)) / scaleY; +}; + +export const DPR = window.devicePixelRatio; + +export const SMA_PERIODS = [5, 20, 60, 120] as const; +export const SMA_STYLE: Record< + (typeof SMA_PERIODS)[number], + { + color: string; + width: number; + } +> = { + '5': { + color: '#EF4444', + width: 2, + }, + '20': { + color: '#F59E0B', + width: 1.5, + }, + '60': { + color: '#3B82F6', + width: 1.2, + }, + '120': { + color: '#8B5CF6', + width: 1, + }, +}; + +export type PriceChartItem = { + x: number; + openY: number; + closeY: number; + lowY: number; + highY: number; + smaY: Record<(typeof SMA_PERIODS)[number], number>; + color: string; +}; + +const CHART_GAP = { + MOBILE: { + PRICE_VERTICAL_MARGIN: { + top: 64, + bottom: 42, + }, + SCORE_VERTICAL_MARGIN: { + top: 28, + bottom: 8, + }, + GRID_GAP: { + width: 48, + height: 28, + }, + }, + DESKTOP: { + PRICE_VERTICAL_MARGIN: { + top: 96, + bottom: 48, + }, + SCORE_VERTICAL_MARGIN: { + top: 32, + bottom: 12, + }, + GRID_GAP: { + width: 64, + height: 36, + }, + }, +}; + +const BAR_SIZE = 8; +const BAR_GAP = BAR_SIZE / 2; +const MIN_SCALE_X = 0.4; +const MAX_SCALE_X = 12.0; +const ZOOM_SPEED = 0.001; + +const StockChartView = ({ + chartData, + updateChart, + period, + country, + chartInteractive = true, + chartHeight, +}: { + chartData: any[]; + updateChart: any; + period: PERIOD_CODE; + country: StockCountryKey; + chartInteractive?: boolean; + chartHeight?: ChartHeight; +}) => { + const isMobile = useIsMobile(); + const chartGap = isMobile ? CHART_GAP.MOBILE : CHART_GAP.DESKTOP; + const priceVerticalMargin = chartGap.PRICE_VERTICAL_MARGIN; + const scoreVerticalMargin = chartGap.SCORE_VERTICAL_MARGIN; + const gridGap = chartGap.GRID_GAP; + + const calculatedChartHeight = chartHeight ?? { price: '1fr', score: !isMobile ? '200px' : '100px' }; + + const pointerCanvasRef = useRef(null); + const [pointerCanvasSize, setPointerCanvasSize] = useState({ width: 0, height: 0 }); + const [priceCanvasSize, setPriceCanvasSize] = useState({ width: 0, height: 0 }); + const [scoreCanvasSize, setScoreCanvasSize] = useState({ width: 0, height: 0 }); + + const [zoomPrice, setZoomPrice, zoomPriceRef] = useStateRef(1); + const chartDataLengthRef = useRef(0); + + const [pointerPos, setPointerPos, pointerPosRef] = useStateRef<{ + curr: Point; + prev: Point; + isMoving: boolean; + }>({ + curr: { x: -1, y: -1 }, + prev: { x: -1, y: -1 }, + isMoving: false, + }); + const [canvasPos, setCanvasPos, canvasPosRef] = useStateRef({ + x: 0, + delta: 0, + }); + + const priceRangeRef = useRef({ + max: 0, + min: 0, + }); + const tradingRangeRef = useRef({ + max: 0, + min: 0, + }); + + const [barSize, setBarSize, barSizeRef] = useStateRef({ + width: BAR_SIZE, + gap: BAR_GAP, + }); + + const pinchRef = useRef<{ centerX: number; radius: number } | null>(null); + + const handlePriceZoom = useCallback((e: WheelEvent) => { + e.preventDefault(); + const zoomPrice = zoomPriceRef.current; + + const deltaY = e.deltaY; + const scale = clamp(zoomPrice * Math.exp(-deltaY * ZOOM_SPEED), 0.125, 8); + setZoomPrice(scale); + }, []); + + const MIN_CANVAS_X = MAX_SCALE_X * (BAR_SIZE + BAR_GAP) * 0.5; + const MAX_CANVAS_X = MAX_SCALE_X * (BAR_SIZE + BAR_GAP) * 0.5; + + const clampDragDelta = useCallback((rawDelta: number) => { + const pointerCanvas = pointerCanvasRef.current; + if (!pointerCanvas) return 0; + + const canvasPos = canvasPosRef.current; + const bar = barSizeRef.current; + + const step = bar.width + bar.gap; + + const width = pointerCanvas.clientWidth; + const minDelta = width - MIN_CANVAS_X - canvasPos.x; + const maxDelta = MAX_CANVAS_X + step * (chartDataLengthRef.current - 1) - canvasPos.x; + + return clamp(rawDelta, minDelta, maxDelta); + }, []); + + const applyZoom = useCallback((pointerX: number, deltaY: number, nextPointerPos: { x: number; y: number }) => { + const pointerCanvas = pointerCanvasRef.current; + if (!pointerCanvas) return; + + const lastIndex = chartDataLengthRef.current - 1; + if (lastIndex < 0) return; + + const { x, delta } = canvasPosRef.current; + const barSize = barSizeRef.current; + + const prevStep = barSize.width + barSize.gap; + + const prevScale = barSize.width / BAR_SIZE; + const nextScale = clamp(prevScale * Math.exp(-deltaY * ZOOM_SPEED), MIN_SCALE_X, MAX_SCALE_X); + if (nextScale === prevScale) return; + + const nextW = BAR_SIZE * nextScale; + const nextG = BAR_GAP * nextScale; + const nextStep = nextW + nextG; + + const width = pointerCanvas.clientWidth; + const minX = width - MIN_CANVAS_X; + const maxX = MAX_CANVAS_X + nextStep * lastIndex; + + const nextVisibleX = clamp(pointerX + ((x + delta - pointerX) / prevStep) * nextStep, minX, maxX); + + setBarSize({ width: nextW, gap: nextG }); + setCanvasPos({ x: nextVisibleX, delta: 0 }); + + const pp = pointerPosRef.current; + setPointerPos({ ...pp, curr: nextPointerPos, prev: nextPointerPos }); + }, []); + + const handleStartPointer = useCallback((e: MouseEvent | TouchEvent) => { + e.preventDefault(); + + const pointerCanvas = pointerCanvasRef.current; + if (!pointerCanvas) return; + + const isTouch = 'touches' in e; + const touchCount = isTouch ? e.touches.length : 0; + + if (isTouch && touchCount > 1) { + const pts = getTouchPoints(e.touches, pointerCanvas); + const c = centroid(pts); + const r = avgRadius(pts, c); + + pinchRef.current = { centerX: c.x, radius: r }; + + const pointerPos = pointerPosRef.current; + setPointerPos({ + ...pointerPos, + isMoving: false, + }); + return; + } + + const localPos = isTouch ? getLocalPos(e.touches, pointerCanvas) : getLocalPos(e, pointerCanvas); + + setPointerPos({ + curr: localPos, + prev: localPos, + isMoving: true, + }); + }, []); + + const handleMovePointer = useCallback( + (e: MouseEvent | TouchEvent) => { + e.preventDefault(); + + const pointerCanvas = pointerCanvasRef.current; + if (!pointerCanvas) return; + + const pointerPos = pointerPosRef.current; + + const isTouch = 'touches' in e; + const touchCount = isTouch ? e.touches.length : 0; + + if (isTouch && touchCount >= 2) { + const pts = getTouchPoints(e.touches, pointerCanvas); + const c = centroid(pts); + const r = avgRadius(pts, c); + + const prev = pinchRef.current; + if (!prev || prev.radius <= 0 || r <= 0) { + pinchRef.current = { centerX: c.x, radius: r }; + return; + } + + const pinchScale = r / prev.radius; + if (Math.abs(pinchScale - 1) < 0.002) { + pinchRef.current = { centerX: c.x, radius: r }; + return; + } + + const deltaY = -Math.log(pinchScale) / ZOOM_SPEED; + + applyZoom(c.x, deltaY, { x: c.x, y: c.y }); + + pinchRef.current = { centerX: c.x, radius: r }; + return; + } + + const localPos = isTouch ? getLocalPos(e.touches, pointerCanvas, 0) : getLocalPos(e, pointerCanvas); + + setPointerPos({ ...pointerPos, curr: localPos }); + if (!pointerPos.isMoving) return; + + const delta = clampDragDelta(localPos.x - pointerPos.prev.x); + + const canvasPos = canvasPosRef.current; + setCanvasPos({ ...canvasPos, delta }); + }, + [applyZoom, clampDragDelta, setPointerPos, setCanvasPos], + ); + + const handleEndPointer = useCallback( + (e: MouseEvent | TouchEvent) => { + e.preventDefault(); + + const pointerCanvas = pointerCanvasRef.current; + if (!pointerCanvas) return; + + const pointerPos = pointerPosRef.current; + const canvasPos = canvasPosRef.current; + + const isTouch = 'touches' in e; + const touchCount = isTouch ? e.touches.length : 0; + + if (isTouch && touchCount >= 2) { + const pts = getTouchPoints(e.touches, pointerCanvas); + const c = centroid(pts); + const r = avgRadius(pts, c); + + setPointerPos({ ...pointerPos, isMoving: false }); + + pinchRef.current = { centerX: c.x, radius: r }; + return; + } + + if (isTouch && touchCount === 1) { + const localPos = getLocalPos(e.touches, pointerCanvas, 0); + + setPointerPos({ curr: localPos, prev: localPos, isMoving: true }); + + pinchRef.current = null; + return; + } + + setPointerPos({ ...pointerPos, isMoving: false }); + setCanvasPos({ x: canvasPos.x + canvasPos.delta, delta: 0 }); + + pinchRef.current = null; + }, + [setPointerPos, setCanvasPos], + ); + + const handleWheel = useCallback( + (e: WheelEvent) => { + e.preventDefault(); + + const pointerCanvas = pointerCanvasRef.current; + if (!pointerCanvas) return; + + const localPos = getLocalPos(e, pointerCanvas); + applyZoom(localPos.x, e.deltaY, localPos); + }, + [applyZoom], + ); + + useEffect(() => { + const pointerCanvas = pointerCanvasRef.current; + if (!pointerCanvas) return; + if (!chartInteractive) return; + + const opts = { passive: false } as AddEventListenerOptions; + + if (!isMobile) { + pointerCanvas.addEventListener('mousedown', handleStartPointer); + window.addEventListener('mousemove', handleMovePointer); + window.addEventListener('mouseup', handleEndPointer); + pointerCanvas.addEventListener('wheel', handleWheel); + } else { + pointerCanvas.addEventListener('touchstart', handleStartPointer, opts); + pointerCanvas.addEventListener('touchmove', handleMovePointer, opts); + pointerCanvas.addEventListener('touchend', handleEndPointer, opts); + pointerCanvas.addEventListener('touchcancel', handleEndPointer, opts); + } + + return () => { + if (!isMobile) { + pointerCanvas.removeEventListener('mousedown', handleStartPointer); + window.removeEventListener('mousemove', handleMovePointer); + window.removeEventListener('mouseup', handleEndPointer); + pointerCanvas.removeEventListener('wheel', handleWheel); + } else { + pointerCanvas.removeEventListener('touchstart', handleStartPointer); + pointerCanvas.removeEventListener('touchmove', handleMovePointer); + pointerCanvas.removeEventListener('touchend', handleEndPointer); + pointerCanvas.removeEventListener('touchcancel', handleEndPointer); + } + }; + }, [chartInteractive, isMobile]); + + const chartItems = useMemo(() => { + const { width, height } = priceCanvasSize; + const len = chartData.length; + + if (!len || width <= 0 || height <= 0) { + return []; + } + + const barWidth = barSize.width + barSize.gap; + + const canvasX = canvasPos.x + canvasPos.delta; + const skip = Math.max(0, Math.floor((canvasX - width) / barWidth)); + const baseX = canvasX - barWidth * skip; + const count = Math.min(Math.floor(baseX / barWidth) + 1, len - skip); + + const startRev = skip; + const endRev = Math.min(skip + count, len - 1); + const visibleCount = Math.max(0, endRev - startRev + 1); + + const chartItems = new Array(visibleCount); + for (let i = 0; i < visibleCount; i++) { + const revIndex = startRev + i; + chartItems[i] = { + ...chartData[len - 1 - revIndex], + idx: revIndex, + }; + } + + return chartItems; + }, [chartData, canvasPos, barSize, priceCanvasSize]); + + const { priceRange, tradingRange } = useMemo(() => { + if (!chartItems.length) + return { + priceRange: priceRangeRef.current, + tradingRange: tradingRangeRef.current, + }; + + let minPrice = Infinity; + let maxPrice = -Infinity; + let maxTrading = -Infinity; + + for (const item of chartItems) { + const low = item.price.low.value; + const high = item.price.high.value; + const trading = item.trading.value; + + if (low < minPrice) minPrice = low; + if (high > maxPrice) maxPrice = high; + if (trading > maxTrading) maxTrading = trading; + + const sma = item.SMA; + for (let p = 0; p < SMA_PERIODS.length; p++) { + const period = SMA_PERIODS[p]; + const v = sma[period].price; + if (v < minPrice) minPrice = v; + if (v > maxPrice) maxPrice = v; + } + } + + const prevPriceRange = priceRangeRef.current; + let nextPriceRange = prevPriceRange; + + if (prevPriceRange.min !== minPrice || prevPriceRange.max !== maxPrice) { + nextPriceRange = { + max: maxPrice, + min: minPrice, + }; + priceRangeRef.current = nextPriceRange; + } + + const prevTradingRange = tradingRangeRef.current; + let nextTradingRange = prevTradingRange; + + if (prevTradingRange.max !== maxTrading) { + nextTradingRange = { + max: maxTrading, + min: 0, + }; + tradingRangeRef.current = nextTradingRange; + } + + return { + priceRange: nextPriceRange, + tradingRange: nextTradingRange, + }; + }, [chartItems]); + + const chartMaxPrice = useMemo(() => { + if (!chartData.length) return 0; + + const maxPriceItem = chartData.reduce((max, item) => Math.max(max, item.price.high.value), -Infinity); + + return formatPrice(maxPriceItem, country); + }, [chartData]); + + const indexToX = useCallback( + (index: number) => { + const { width, gap } = barSizeRef.current; + const { x, delta } = canvasPosRef.current; + const barWidth = width + gap; + const canvasX = x + delta; + return canvasX - index * barWidth; + }, + [barSizeRef.current, canvasPosRef.current], + ); + + const xToIndex = useCallback( + (x: number) => { + const { width, gap } = barSizeRef.current; + const { x: currX, delta } = canvasPosRef.current; + const barWidth = width + gap; + const canvasX = currX + delta; + return Math.floor((canvasX - x + barWidth / 2) / barWidth); + }, + [barSizeRef.current, canvasPosRef.current], + ); + + const { priceToY, yToPrice } = useMemo(() => { + const { height } = priceCanvasSize; + + return { + priceToY: (price: number) => valueToY(price, height, priceVerticalMargin, priceRange, zoomPrice), + yToPrice: (y: number) => yToValue(y, height, priceVerticalMargin, priceRange, zoomPrice), + }; + }, [priceCanvasSize.height, priceVerticalMargin, priceRange, zoomPrice]); + + const { scoreToY, yToScore } = useMemo(() => { + const { height } = scoreCanvasSize; + const scoreRange = { max: 100, min: 0 }; + + return { + scoreToY: (score: number) => valueToY(score, height, scoreVerticalMargin, scoreRange), + yToScore: (y: number) => yToValue(y, height, scoreVerticalMargin, scoreRange), + }; + }, [scoreCanvasSize.height, scoreVerticalMargin]); + + const tradingToY = useCallback( + (value: number) => { + const { height } = scoreCanvasSize; + + return valueToY(value, height, scoreVerticalMargin, tradingRange); + }, + [scoreCanvasSize.height, scoreVerticalMargin, tradingRange], + ); + + const indexedDates: IndexedYmd[] = useMemo(() => { + const chartLen = chartData.length; + if (!chartLen) return []; + + const marketOpenDays = OPEN_DAYS[period][country] as YMD[]; + + const chartYmds = new Array(chartLen); + const chartMetas = new Array(chartLen); + for (let i = 0; i < chartLen; i++) { + const ymd = formatYMD(chartData[i].date); + chartYmds[i] = ymd; + chartMetas[i] = parseYmdMeta(ymd); + } + + const openDayMetas = new Array(marketOpenDays.length); + const openDayKeys = new Array(marketOpenDays.length); + for (let i = 0; i < marketOpenDays.length; i++) { + const meta = parseYmdMeta(marketOpenDays[i]); + openDayMetas[i] = meta; + openDayKeys[i] = pickMetaKeyByPeriod(period, meta); + } + + const firstChartKey = pickMetaKeyByPeriod(period, chartMetas[0]); + const lastChartKey = pickMetaKeyByPeriod(period, chartMetas[chartLen - 1]); + + const firstOpenDayIdx = lowerBoundNumber(openDayKeys, firstChartKey); + const lastOpenDayIdx = lowerBoundNumber(openDayKeys, lastChartKey); + + const prefixEndIdx = firstOpenDayIdx - 1; + const suffixStartIdx = Math.min(marketOpenDays.length, lastOpenDayIdx + 1); + + const prefixLen = Math.max(0, prefixEndIdx + 1); + const suffixLen = Math.max(0, marketOpenDays.length - suffixStartIdx); + + const out = new Array(prefixLen + chartLen + suffixLen); + + let writePos = 0; + + for (let i = 0; i < prefixLen; i++) { + out[writePos++] = { + ymd: marketOpenDays[i], + meta: openDayMetas[i], + idx: chartLen + prefixEndIdx - i, + }; + } + + for (let i = 0; i < chartLen; i++) { + out[writePos++] = { + ymd: chartYmds[i], + meta: chartMetas[i], + idx: chartLen - i - 1, + }; + } + + for (let i = 0; i < suffixLen; i++) { + const openIdx = suffixStartIdx + i; + out[writePos++] = { + ymd: marketOpenDays[openIdx], + meta: openDayMetas[openIdx], + idx: -i - 1, + }; + } + + return out; + }, [chartData, period, country]); + + const labeledGridDates: LabeledGridYmd[] = useMemo(() => { + if (!indexedDates.length) return []; + + const barPitch = barSize.width + barSize.gap; + const minGridGap = gridGap.width; + + const gridPoints: LabeledGridYmd[] = []; + + const makeGridPoint = (src: IndexedYmd, unit: DateUnit): LabeledGridYmd => ({ + ymd: src.ymd, + idx: src.idx, + label: formatGridLabel(src.meta, unit), + }); + + gridPoints.push(makeGridPoint(indexedDates[0], 'Y')); + + let lastPlacedX = 0; + let lastPlacedUnit: DateUnit = 'Y'; + let prevMeta: YmdMeta = indexedDates[0].meta; + + for (let i = 1; i < indexedDates.length; i++) { + const src = indexedDates[i]; + const x = i * barPitch; + + const unit = getUnitByMetaChange(prevMeta, src.meta); + prevMeta = src.meta; + + const isFarEnough = x - lastPlacedX >= minGridGap; + const isHigherUnit = DATE_UNIT_PRIORITY[unit] > DATE_UNIT_PRIORITY[lastPlacedUnit]; + if (!isFarEnough && !isHigherUnit) continue; + + const nextPoint = makeGridPoint(src, unit); + if (isFarEnough) gridPoints.push(nextPoint); + else gridPoints[gridPoints.length - 1] = nextPoint; + + lastPlacedX = x; + lastPlacedUnit = unit; + } + + return gridPoints; + }, [indexedDates, barSize, gridGap]); + + const dateGrid: GridItem[] = useMemo(() => { + const res: GridItem[] = []; + const canvasWidth = pointerCanvasSize.width; + + for (const item of labeledGridDates) { + const x = indexToX(item.idx); + if (x < 0 || x > canvasWidth) continue; + + res.push({ + pos: { x }, + value: item.ymd, + text: item.label, + }); + } + + return res; + }, [labeledGridDates, indexToX, pointerCanvasSize.width]); + + const priceChartItems = useMemo(() => { + if (!chartItems.length) { + return []; + } + + return chartItems.map(({ price, SMA, idx }) => { + const x = indexToX(idx); + + const openY = priceToY(price.open.value); + const closeY = priceToY(price.close.value); + const lowY = priceToY(price.low.value); + const highY = priceToY(price.high.value); + + const smaY = {} as Record<(typeof SMA_PERIODS)[number], number>; + for (let p = 0; p < SMA_PERIODS.length; p++) { + const period = SMA_PERIODS[p]; + smaY[period] = priceToY(SMA[period].price); + } + + return { + x, + openY, + closeY, + lowY, + highY, + smaY: smaY, + color: deltaToChartColor(price.close.value - price.open.value), + }; + }); + }, [chartItems, indexToX, priceToY]); + + const scoreChartItems = useMemo(() => { + if (!chartItems.length) return []; + + return chartItems + .map(({ score, idx }) => { + const { value, delta } = score; + if (value === null) return; + const x = indexToX(idx); + + const y = scoreToY(value); + + return { + pos: { + x, + y, + }, + value, + delta, + }; + }) + .filter(Boolean); + }, [chartItems, indexToX, scoreToY]); + + const tradingChartItems = useMemo(() => { + if (!chartItems.length) return []; + + const bottom = tradingToY(0); + return chartItems.map(({ trading, idx }) => { + const { value, volume, delta } = trading; + const x = indexToX(idx); + return { + pos: { + x, + y: tradingToY(value), + }, + value, + volume, + delta, + bottom, + }; + }); + }, [chartItems, indexToX, tradingToY]); + + const createYAxisGrid = useCallback( + ({ + height, + margin, + yToValue, + valueToY, + valueRange, + formatText, + zoomPrice = 1, + }: { + height: number; + margin: { top: number; bottom: number }; + yToValue: (y: number) => number; + valueToY: (value: number) => number; + valueRange: ValueRange; + formatText: (value: number) => string; + zoomPrice?: number; + }) => { + const gapHeight = gridGap.height; + + const plotTop = margin.top; + const plotBottom = height - margin.bottom; + + const scaleY = getScaleY(plotTop, plotBottom, valueRange); + const axis = AXIS_SCALE_VALUES.find((v) => (v * scaleY) / zoomPrice >= gapHeight) ?? 1e9; + + const start = Math.floor(yToValue(height) / axis); + const end = Math.ceil(yToValue(0) / axis); + + const grid: GridItem[] = []; + for (let m = start; m <= end; m++) { + const value = m * axis; + const y = valueToY(value); + + if (y < 0 || y > height) continue; + + grid.push({ + value, + text: formatText(value), + pos: { y }, + }); + } + + return grid; + }, + [gridGap.height], + ); + + const priceGrid = useMemo(() => { + return createYAxisGrid({ + height: priceCanvasSize.height, + margin: priceVerticalMargin, + yToValue: yToPrice, + valueToY: priceToY, + valueRange: priceRange, + formatText: (value) => formatPrice(value, country), + zoomPrice, + }); + }, [createYAxisGrid, priceCanvasSize.height, priceVerticalMargin, priceToY, yToPrice, priceRange, zoomPrice]); + + const scoreGrid = useMemo(() => { + return createYAxisGrid({ + height: scoreCanvasSize.height, + margin: scoreVerticalMargin, + yToValue: yToScore, + valueToY: scoreToY, + valueRange: { min: 0, max: 100 }, + formatText: (value) => value.toString(), + }); + }, [createYAxisGrid, scoreCanvasSize.height, scoreVerticalMargin, scoreToY, yToScore]); + + const recentPrice = useMemo(() => { + if (!chartData?.length) return; + + const recentChartItem = chartData[chartData.length - 1]; + const value = recentChartItem.price.close.value; + const delta = value - recentChartItem.price.open.value; + + return { + pos: { + y: priceToY(value), + }, + value, + text: formatPrice(value, country), + delta, + }; + }, [chartData, priceToY]); + + const lastPrice = useMemo(() => { + if (!chartItems?.length) return; + + const lastChartItem = chartItems[0]; + const value = lastChartItem.price.close.value; + const delta = value - lastChartItem.price.open.value; + + return { + pos: { + y: priceToY(value), + }, + value, + text: formatPrice(value, country), + delta, + }; + }, [chartItems, priceToY]); + + const pointerIndex = useMemo(() => { + if (isMobile) return; + const { width, height } = pointerCanvasSize; + const { curr } = pointerPos; + + if (curr.x < 0 || curr.x > width || curr.y < 0 || curr.y > height) return; + + return xToIndex(curr.x); + }, [isMobile, pointerPos, xToIndex]); + + const pointerChartItem = useMemo(() => { + if (pointerIndex === undefined) return; + + return chartItems.find((item) => item.idx === pointerIndex); + }, [pointerIndex, chartItems]); + + const { pointerPriceInfo, pointerScoreInfo } = useMemo(() => { + if (!pointerChartItem) return { pointerPriceInfo: undefined, pointerScoreInfo: undefined }; + + return { + pointerPriceInfo: { + price: pointerChartItem.price, + SMA: pointerChartItem.SMA, + }, + pointerScoreInfo: { + trading: pointerChartItem.trading, + score: pointerChartItem.score, + }, + }; + }, [pointerChartItem]); + + const { pointerPriceY, pointerScoreY } = useMemo(() => { + const { width, height } = pointerCanvasSize; + const { x, y } = pointerPos.curr; + + if (isMobile || x < 0 || x > width || y < 0 || y > height) { + return { pointerPriceY: undefined, pointerScoreY: undefined }; + } + + const priceH = priceCanvasSize.height; + + const priceY = y <= priceH ? y : undefined; + const scoreY = y > priceH && y <= height ? y - priceH : undefined; + + return { + pointerPriceY: priceY, + pointerScoreY: scoreY, + }; + }, [isMobile, pointerPos, pointerCanvasSize, priceCanvasSize.height]); + + const pointerPriceLabel = useMemo(() => { + if (!pointerPriceY) return; + + const price = yToPrice(pointerPriceY); + return { + value: price, + text: formatPrice(price, country), + pos: { + y: pointerPriceY, + }, + }; + }, [pointerPriceY, country]); + + const pointerScoreLabel = useMemo(() => { + if (!pointerScoreY) return; + + const score = clamp(yToScore(pointerScoreY), 0, 100); + return { + value: score, + text: score.toFixed(0), + pos: { + y: pointerScoreY, + }, + }; + }, [pointerScoreY]); + + const pointerDateLabel = useMemo(() => { + if (!pointerIndex) return; + + const date = indexedDates.find((item) => item.idx === pointerIndex); + if (!date) return; + + return { + value: date.ymd, + text: date.ymd, + pos: { + x: indexToX(date.idx), + }, + }; + }, [pointerIndex, indexedDates, indexToX]); + + const pointerInfo = useMemo(() => { + if (isMobile) return; + const { width, height } = pointerCanvasSize; + + const { curr } = pointerPos; + if (curr.x < 0 || curr.x > width || curr.y < 0 || curr.y > height) return; + + const y = curr.y; + + const index = xToIndex(curr.x); + const x = indexToX(index); + + return { + pos: { + x, + y, + }, + }; + }, [isMobile, pointerCanvasSize, pointerPos, xToIndex, indexToX]); + + useEffect(() => { + if (!chartItems?.length) return; + + const oldItem = chartItems[chartItems.length - 1]; + if (oldItem.idx !== chartData.length - 1) return; + + updateChart(formatDateISO(oldItem.date)); + }, [chartItems]); + + useEffect(() => { + chartDataLengthRef.current = chartData.length; + }, [chartData]); + + const [firstLoading, setFirstLoading] = useState(false); + useEffect(() => { + if (!chartMaxPrice) return; + setFirstLoading(true); + }, [chartMaxPrice]); + + useEffect(() => { + const pointerCanvas = pointerCanvasRef.current; + if (!pointerCanvas) return; + + setZoomPrice(1); + setBarSize({ width: BAR_SIZE, gap: BAR_GAP }); + + setCanvasPos({ + x: pointerCanvas.clientWidth - BAR_SIZE * 4, + delta: 0, + }); + }, [period, firstLoading]); + + const canvasHeight = useMemo(() => { + return { + price: priceCanvasSize.height, + score: scoreCanvasSize.height, + }; + }, [priceCanvasSize, scoreCanvasSize]); + + return ( + + + + + + + + + + + + + + + + + ); +}; + +export default StockChartView; diff --git a/src/components/Search/StockChart/DateLabel.tsx b/src/components/Search/StockChart/DateLabel.tsx new file mode 100644 index 00000000..d97de3e6 --- /dev/null +++ b/src/components/Search/StockChart/DateLabel.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import StockChartGridLabel, { GridItem } from './GridLabel'; +import { ChartLabel, DateLabelContainer } from './StockChart.Style'; + +const StockChartDateLabel = ({ dateGrid, pointerDate }: { dateGrid: GridItem[]; pointerDate?: GridItem }) => { + return ( + + + {pointerDate && ( + + {pointerDate.value} + + )} + + ); +}; + +export default React.memo(StockChartDateLabel); diff --git a/src/components/Search/StockChart/ExtremePrice.tsx b/src/components/Search/StockChart/ExtremePrice.tsx new file mode 100644 index 00000000..9912f686 --- /dev/null +++ b/src/components/Search/StockChart/ExtremePrice.tsx @@ -0,0 +1,106 @@ +import { useMemo } from 'react'; +import React from 'react'; +import { StockCountryKey } from '@ts/StockCountry'; +import { getPriceText } from '@utils/Number'; +import { getDiffText } from '@utils/Number'; +import DownSVG from '@assets/icons/down.svg?react'; +import UpSVG from '@assets/icons/up.svg?react'; +import { ExtremePriceContainer, ExtremePriceLabel } from './StockChart.Style'; + +const EXTREME = { + MAX: { + label: '최대', + }, + MIN: { + label: '최소', + }, +}; + +type ExtremeKey = 'MAX' | 'MIN'; +type Extreme = { + key: ExtremeKey; + price: number; + pos: { x: number; y: number } | undefined; + delta: number; +}; + +const StockChartExtremePrice = ({ + priceCanvasSize, + chartItems, + indexToX, + priceToY, + recentPrice, + country, +}: { + priceCanvasSize: any; + chartItems: any[]; + indexToX: any; + priceToY: any; + recentPrice: any; + country: StockCountryKey; +}) => { + const extremePrice: Extreme[] = useMemo(() => { + if (!chartItems?.length) return []; + + const width = priceCanvasSize.width; + const recent = recentPrice.value; + + const MIN: Extreme = { + key: 'MIN', + price: Infinity, + pos: undefined, + delta: 0, + }; + const MAX: Extreme = { + key: 'MAX', + price: -Infinity, + pos: undefined, + delta: 0, + }; + + for (const item of chartItems) { + const x = indexToX(item.idx); + if (x <= 0 || x >= width) continue; + + const low = item.price.low.value; + const high = item.price.high.value; + + if (low < MIN.price) { + MIN.price = low; + MIN.pos = { x, y: priceToY(low) }; + MIN.delta = (recent / low - 1) * 100; + } + + if (high > MAX.price) { + MAX.price = high; + MAX.pos = { x, y: priceToY(high) }; + MAX.delta = (recent / high - 1) * 100; + } + } + + const out: Extreme[] = []; + if (MIN.pos) out.push(MIN); + if (MAX.pos) out.push(MAX); + return out; + }, [chartItems, priceToY, recentPrice]); + + return ( + + {extremePrice.map(({ pos, price, delta, key }) => { + if (!pos) return null; + return ( + +

    + {`${EXTREME[key].label} : ${getPriceText(country, price, { currencyText: true })} + (${getDiffText({ percentDiff: delta })})`} +

    + + {key == 'MAX' ? : } +
    + ); + })} +
    + ); +}; + +export default React.memo(StockChartExtremePrice); diff --git a/src/components/Search/StockChart/GridCanvas.tsx b/src/components/Search/StockChart/GridCanvas.tsx new file mode 100644 index 00000000..f495246f --- /dev/null +++ b/src/components/Search/StockChart/GridCanvas.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { drawLine } from '@utils/Canvas'; +import useHiDPICanvas from '@hooks/useHiDPICanvas'; +import useRafCanvasDraw from '@hooks/useRafCanvasDraw'; +import { GridItem } from './GridLabel'; +import { ViewCanvas } from './StockChart.Style'; + +const drawGrid = ( + ctx: CanvasRenderingContext2D, + priceGrid: GridItem[], + scoreGrid: GridItem[], + dateGrid: GridItem[], + height: number, +) => { + ctx.globalAlpha = 0.85; + + ctx.strokeStyle = '#303033'; + ctx.lineWidth = 1; + + priceGrid.forEach((item) => { + drawLine(ctx, [ + [0, item.pos.y], + [ctx.canvas.width, item.pos.y], + ]); + }); + + scoreGrid.forEach((item) => { + drawLine(ctx, [ + [0, item.pos.y! + height], + [ctx.canvas.width, item.pos.y! + height], + ]); + }); + + dateGrid.forEach((item) => { + drawLine(ctx, [ + [item.pos.x, 0], + [item.pos.x, ctx.canvas.height], + ]); + }); +}; + +const StockChartGridCanvas = ({ + canvasHeight, + dateGrid, + priceGrid, + scoreGrid, +}: { + canvasHeight: { price: number; score: number }; + dateGrid: GridItem[]; + priceGrid: GridItem[]; + scoreGrid: GridItem[]; +}) => { + const { canvasRef, redrawTick } = useHiDPICanvas(); + + const draw = (ctx: CanvasRenderingContext2D) => { + drawGrid(ctx, priceGrid, scoreGrid, dateGrid, canvasHeight.price); + }; + + useRafCanvasDraw(canvasRef, draw, [redrawTick, canvasHeight.price, dateGrid, priceGrid, scoreGrid]); + + return ; +}; + +export default React.memo(StockChartGridCanvas); diff --git a/src/components/Search/StockChart/GridLabel.tsx b/src/components/Search/StockChart/GridLabel.tsx new file mode 100644 index 00000000..5c0c5dd1 --- /dev/null +++ b/src/components/Search/StockChart/GridLabel.tsx @@ -0,0 +1,35 @@ +import React, { useMemo } from 'react'; +import { ChartLabel } from './StockChart.Style'; + +export type GridItem = { + pos: { + x?: number; + y?: number; + }; + value: string | number; + text: string; + delta?: number; +}; + +const StockChartGridLabel = React.memo( + ({ name, gridItems, baseText }: { name: string; gridItems: GridItem[]; baseText: string }) => { + const labels = useMemo( + () => + gridItems.map((e) => ( + + {e.text} + + )), + [gridItems], + ); + + return ( + <> + {baseText} + {labels} + + ); + }, +); + +export default StockChartGridLabel; diff --git a/src/components/Search/StockChart/PointerCanvas.tsx b/src/components/Search/StockChart/PointerCanvas.tsx new file mode 100644 index 00000000..561bebb5 --- /dev/null +++ b/src/components/Search/StockChart/PointerCanvas.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { CanvasSize, drawLine } from '@utils/Canvas'; +import useHiDPICanvas from '@hooks/useHiDPICanvas'; +import useRafCanvasDraw from '@hooks/useRafCanvasDraw'; +import { ViewCanvas } from './StockChart.Style'; + +const drawPointer = (ctx: CanvasRenderingContext2D, pointerInfo: any, canvasSize: CanvasSize) => { + if (!pointerInfo) return; + const { pos } = pointerInfo; + + ctx.lineWidth = 1.5; + ctx.strokeStyle = '#9A9C9E'; + ctx.setLineDash([4, 2]); + + drawLine(ctx, [ + [pos.x, 0], + [pos.x, canvasSize.height], + ]); + drawLine(ctx, [ + [0, pos.y], + [canvasSize.width, pos.y], + ]); +}; + +const StockChartPointerCanvas = ({ + canvasRef, + onSizeChange, + canvasHeight, + pointerInfo, +}: { + canvasRef: React.RefObject; + onSizeChange: (size: CanvasSize) => void; + canvasHeight: { price: number; score: number }; + pointerInfo: any; +}) => { + const { canvasSize, redrawTick } = useHiDPICanvas({ + canvasRef, + onResize: onSizeChange, + }); + + const draw = (ctx: CanvasRenderingContext2D) => { + drawPointer(ctx, pointerInfo, canvasSize); + }; + + useRafCanvasDraw(canvasRef, draw, [redrawTick, canvasSize.width, canvasSize.height, pointerInfo]); + + return ; +}; + +export default React.memo(StockChartPointerCanvas); diff --git a/src/components/Search/StockChart/PriceChart.tsx b/src/components/Search/StockChart/PriceChart.tsx new file mode 100644 index 00000000..d496d9dc --- /dev/null +++ b/src/components/Search/StockChart/PriceChart.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { CanvasSize, drawLine } from '@utils/Canvas'; +import useHiDPICanvas from '@hooks/useHiDPICanvas'; +import useRafCanvasDraw from '@hooks/useRafCanvasDraw'; +import { PriceChartItem, SMA_PERIODS, SMA_STYLE } from './ChartView'; +import { GridItem } from './GridLabel'; +import { ViewCanvas, deltaToChartColor } from './StockChart.Style'; + +const drawBar = (ctx: CanvasRenderingContext2D, priceChartItems: PriceChartItem[], barSize: any) => { + ctx.globalAlpha = 1; + priceChartItems.forEach((item) => { + const color = item.color; + + const x = item.x; + + ctx.strokeStyle = color; + ctx.lineWidth = 1.5; + drawLine(ctx, [ + [x, item.highY], + [x, item.lowY], + ]); + + const bodyTop = Math.min(item.openY, item.closeY); + const bodyHeight = Math.max(Math.abs(item.openY - item.closeY), 1); + ctx.fillStyle = color; + ctx.fillRect(x - barSize.width / 2, bodyTop, barSize.width, bodyHeight); + }); +}; + +const drawSMA = (ctx: CanvasRenderingContext2D, priceChartItems: PriceChartItem[]) => { + ctx.globalAlpha = 0.85; + SMA_PERIODS.forEach((period) => { + ctx.strokeStyle = SMA_STYLE[period].color; + ctx.lineWidth = SMA_STYLE[period].width; + drawLine( + ctx, + priceChartItems.map((item) => [item.x, item.smaY[period]]), + ); + }); +}; + +const drawRecentPriceLine = (ctx: CanvasRenderingContext2D, recentPrice?: GridItem) => { + if (!recentPrice) return; + + ctx.globalAlpha = 1; + ctx.strokeStyle = deltaToChartColor(recentPrice.delta!); + ctx.lineWidth = 1; + ctx.setLineDash([4, 4]); + + drawLine(ctx, [ + [0, recentPrice.pos.y!], + [ctx.canvas.width, recentPrice.pos.y!], + ]); +}; + +const StockPriceChart = ({ + onSizeChange, + priceChartItems, + barSize, + recentPrice, +}: { + onSizeChange: (size: CanvasSize) => void; + priceChartItems: PriceChartItem[]; + barSize: any; + recentPrice?: GridItem; +}) => { + const { canvasRef, canvasSize, redrawTick } = useHiDPICanvas({ + onResize: onSizeChange, + }); + + const draw = (ctx: CanvasRenderingContext2D) => { + drawBar(ctx, priceChartItems, barSize); + drawSMA(ctx, priceChartItems); + if (recentPrice) { + drawRecentPriceLine(ctx, recentPrice); + } + }; + + useRafCanvasDraw(canvasRef, draw, [ + redrawTick, + canvasSize.width, + canvasSize.height, + priceChartItems, + barSize, + recentPrice, + ]); + + return ; +}; + +export default React.memo(StockPriceChart); diff --git a/src/components/Search/StockChart/PriceInfo.tsx b/src/components/Search/StockChart/PriceInfo.tsx new file mode 100644 index 00000000..bb28a5e0 --- /dev/null +++ b/src/components/Search/StockChart/PriceInfo.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { StockCountryKey } from '@ts/StockCountry'; +import { getPriceText } from '@utils/Number'; +import { getDiffText } from '@utils/Number'; +import { SMA_PERIODS, SMA_STYLE } from './ChartView'; +import { deltaToChartColor } from './StockChart.Style'; +import { + InfoHeader, + InfoHeaderItemContainer, + InfoHeaderItemContent, + InfoHeaderItemValueText, +} from './StockChart.Style'; + +const CHART_PRICE_FIELDS = [ + { + key: 'open', + oldKey: 'openPrice', + label: '시가', + }, + { + key: 'high', + oldKey: 'highPrice', + label: '고가', + }, + { + key: 'low', + oldKey: 'lowPrice', + label: '저가', + }, + { + key: 'close', + oldKey: 'closePrice', + label: '종가', + }, +]; + +const StockChartPriceInfo = ({ + pointerPriceInfo, + country, + isMobile, +}: { + pointerPriceInfo: any; + country: StockCountryKey; + isMobile: boolean; +}) => { + const { price, SMA } = pointerPriceInfo ?? {}; + const hasValue = price && SMA; + + return ( + + {!isMobile && ( + + {CHART_PRICE_FIELDS.map(({ key, label }) => ( + + {label} + {hasValue && ( + + {getPriceText(country, price[key].value, { space: false })} + {getDiffText({ percentDiff: price[key].delta * 100 })} + + )} + + ))} + + )} + +

    이동평균선

    + {SMA_PERIODS.map((range) => ( + + {range} + {hasValue && ( + + {getPriceText(country, SMA[range].price, { space: false })} + + )} + + ))} +
    +
    + ); +}; + +export default React.memo(StockChartPriceInfo); diff --git a/src/components/Search/StockChart/PriceLabel.tsx b/src/components/Search/StockChart/PriceLabel.tsx new file mode 100644 index 00000000..f58caf61 --- /dev/null +++ b/src/components/Search/StockChart/PriceLabel.tsx @@ -0,0 +1,48 @@ +import React, { useEffect, useRef } from 'react'; +import StockChartGridLabel from './GridLabel'; +import { ChartLabel, ChartLabelContainer, deltaToChartColor } from './StockChart.Style'; + +const StockChartPriceLabel = ({ + priceGrid, + recentPrice, + lastPrice, + chartMaxPrice, + pointerPrice, + handlePriceZoom, +}: any) => { + const priceLabelRef = useRef(null); + + useEffect(() => { + const priceLabel = priceLabelRef.current; + if (!priceLabel) return; + + priceLabel.addEventListener('wheel', handlePriceZoom, { passive: false }); + + return () => { + priceLabel.removeEventListener('wheel', handlePriceZoom); + }; + }, [handlePriceZoom]); + + return ( + + + {lastPrice && ( + + {lastPrice.text} + + )} + {recentPrice && ( + + {recentPrice.text} + + )} + {pointerPrice && ( + + {pointerPrice.text} + + )} + + ); +}; + +export default React.memo(StockChartPriceLabel); diff --git a/src/components/Search/StockChart/ScoreChart.tsx b/src/components/Search/StockChart/ScoreChart.tsx new file mode 100644 index 00000000..fcc13155 --- /dev/null +++ b/src/components/Search/StockChart/ScoreChart.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { CanvasSize, drawLine } from '@utils/Canvas'; +import useHiDPICanvas from '@hooks/useHiDPICanvas'; +import useRafCanvasDraw from '@hooks/useRafCanvasDraw'; +import { ViewCanvas, deltaToChartColor } from './StockChart.Style'; + +const drawScoreLineChart = (ctx: CanvasRenderingContext2D, scoreChartItems: any[]) => { + if (!scoreChartItems.length) return; + ctx.globalAlpha = 1; + ctx.strokeStyle = '#64FFEA'; + ctx.lineWidth = 2; + + drawLine( + ctx, + scoreChartItems.map(({ pos }) => [pos.x, pos.y]), + ); +}; + +const drawTradingBarChart = (ctx: CanvasRenderingContext2D, tradingChartItems: any[], barSize: any) => { + if (!tradingChartItems.length) return; + ctx.globalAlpha = 0.75; + + tradingChartItems.forEach(({ pos, delta, bottom }) => { + ctx.fillStyle = deltaToChartColor(delta); + ctx.fillRect(pos.x - barSize.width / 2, pos.y, barSize.width, bottom - pos.y); + }); +}; + +const StockScoreChart = ({ + onSizeChange, + scoreChartItems, + tradingChartItems, + barSize, +}: { + onSizeChange: (size: CanvasSize) => void; + scoreChartItems: any[]; + tradingChartItems: any[]; + barSize: any; +}) => { + const { canvasRef, canvasSize, redrawTick } = useHiDPICanvas({ + onResize: onSizeChange, + }); + + const draw = (ctx: CanvasRenderingContext2D) => { + drawTradingBarChart(ctx, tradingChartItems, barSize); + drawScoreLineChart(ctx, scoreChartItems); + }; + + useRafCanvasDraw(canvasRef, draw, [ + redrawTick, + canvasSize.width, + canvasSize.height, + barSize, + tradingChartItems, + scoreChartItems, + ]); + + return ; +}; + +export default React.memo(StockScoreChart); diff --git a/src/components/Search/StockChart/ScoreInfo.tsx b/src/components/Search/StockChart/ScoreInfo.tsx new file mode 100644 index 00000000..3273ac74 --- /dev/null +++ b/src/components/Search/StockChart/ScoreInfo.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { getDiffText } from '@utils/Number'; +import { InfoHeader, InfoHeaderItemContainer, InfoHeaderItemValueText } from './StockChart.Style'; +import { deltaToChartColor } from './StockChart.Style'; + +const formatVolume = (volume: number) => { + const unit = [ + { + type: 'B', + num: 1e9, + }, + { + type: 'M', + num: 1e6, + }, + { + type: 'K', + num: 1e3, + }, + { + type: '', + num: 1, + }, + ]; + const a = unit.find(({ num }) => volume >= num); + + return a && (volume / a.num).toFixed(2) + a.type; +}; + +const StockChartScoreInfo = ({ pointerScoreInfo }: { pointerScoreInfo: any }) => { + const { trading, score } = pointerScoreInfo ?? {}; + + return ( + + +

    거래량

    + {trading && ( + + {trading.value === 0 ? '-' : formatVolume(trading.value)} + {isFinite(trading.delta) && ({getDiffText({ percentDiff: trading.delta * 100 })})} + + )} +

    / 인간지표

    + {score && ( + + {!score.value ? '-' : `${score.value}점`} + {!!score.delta && ({getDiffText({ valueDiff: score.delta })}점)} + + )} +
    +
    + ); +}; + +export default React.memo(StockChartScoreInfo); diff --git a/src/components/Search/StockChart/ScoreLabel.tsx b/src/components/Search/StockChart/ScoreLabel.tsx new file mode 100644 index 00000000..c635d1f2 --- /dev/null +++ b/src/components/Search/StockChart/ScoreLabel.tsx @@ -0,0 +1,21 @@ +import { useRef } from 'react'; +import React from 'react'; +import StockChartGridLabel from './GridLabel'; +import { ChartLabel, ChartLabelContainer } from './StockChart.Style'; + +const StockChartScoreLabel = ({ scoreGrid, pointerScore }: any) => { + const scoreLabelRef = useRef(null); + + return ( + + + {pointerScore && ( + + {pointerScore.text} + + )} + + ); +}; + +export default React.memo(StockChartScoreLabel); diff --git a/src/components/Search/StockChart/StockChart.Style.ts b/src/components/Search/StockChart/StockChart.Style.ts index 8893abaa..03a0e187 100644 --- a/src/components/Search/StockChart/StockChart.Style.ts +++ b/src/components/Search/StockChart/StockChart.Style.ts @@ -1,17 +1,21 @@ import styled from '@emotion/styled'; import { media, theme, themeColor } from '@styles/themes'; -export const StockChartContainer = styled.div({ +export const ChartContainer = styled.div({ display: 'flex', flexDirection: 'column', gap: '18px', + height: '100%', + width: '100%', [media[0]]: { gap: '12px', }, }); -export const StockChartHeader = styled.div({ +// Header + +export const ChartHeader = styled.div({ display: 'flex', alignItems: 'center', justifyContent: 'space-between', @@ -23,7 +27,7 @@ export const StockChartHeader = styled.div({ }, }); -export const StockChartHeaderContents = styled.div({ +export const ChartHeaderContents = styled.div({ display: 'flex', gap: '8px', @@ -32,7 +36,7 @@ export const StockChartHeaderContents = styled.div({ }, }); -export const StockChartHeaderItem = styled.div( +export const ChartHeaderItem = styled.div( ({ background }: { background?: themeColor }) => ({ background: background ? theme.colors[background] : theme.colors.transparent, }), @@ -48,214 +52,219 @@ export const StockChartHeaderItem = styled.div( }, ); -export const StockChartStyledCanvas = styled.canvas({ - position: 'absolute', - - width: '100%', - height: '100%', -}); +// View -export const StockChartGridContainer = styled.div({ - display: 'grid', - gridTemplateRows: '600px 40px', - gridTemplateColumns: '42px auto 72px', +export interface ChartHeight { + price: string; + score: string; +} - fontSize: '15px', +export const ViewContainer = styled.div( + ({ chartHeight }: { chartHeight: ChartHeight }) => { + const { price, score } = chartHeight ?? {}; - [media[0]]: { - gridTemplateRows: '400px 19px', - gridTemplateColumns: '28px auto 48px', + return { + gridTemplateRows: `${price ?? '1fr'} ${score ?? '200px'} auto`, - fontSize: '11px', + [media[0]]: { + gridTemplateRows: `${price ?? '1fr'} ${score ?? '100px'} auto`, + }, + }; }, - - '> div': { + { position: 'relative', + display: 'grid', + gridTemplateColumns: '1fr auto', + width: '100%', - overflow: 'hidden', - }, + flexGrow: '1', + height: '700px', - [media[0]]: { - gridTemplateRows: '400px 19px', - gridTemplateColumns: '28px auto 48px', - - fontSize: '11px', + [media[0]]: { + height: '400px', + }, }, -}); +); -export const ChartLabelBase = styled.span({ +export const ViewCanvasContainer = styled.div({ + position: 'relative', + width: '100%', + height: '100%', + borderBottom: `2px solid ${theme.colors.sub_gray10}`, + borderRight: `2px solid ${theme.colors.sub_gray10}`, boxSizing: 'border-box', - padding: '4px 12px', +}); - color: 'transparent', - lineHeight: 1, - whiteSpace: 'nowrap', +export const ViewCanvas = styled.canvas( + ({ type, expand }: { type?: 'POINTER' | 'GRID'; expand?: string }) => ({ + height: expand ? `calc(100% + ${expand})` : '100%', + zIndex: type === 'GRID' ? 0 : type === 'POINTER' ? 2 : 1, + }), + { + position: 'absolute', + width: '100%', + top: '0', + left: '0', + }, +); - borderColor: 'transparent', - borderStyle: 'solid', - borderWidth: '2px', +// Label - [media[0]]: { - padding: '4px 8px', - }, +export const ChartLabelContainer = styled.div({ + position: 'relative', + overflow: 'hidden', + userSelect: 'none', + borderBottom: `2px solid ${theme.colors.grayscale90}`, + flexShrink: '0', + boxSizing: 'border-box', + height: '100%', }); export const ChartLabel = styled.span( + ({ type, y, x, color }: { type?: 'FILL' | 'STROKE' | 'MOCK'; y?: number; x?: number; color?: string }) => + ({ + visibility: type === 'MOCK' ? 'hidden' : 'visible', + position: type === 'MOCK' ? 'static' : 'absolute', + top: y ?? 0, + left: x ?? 0, + color: type === 'STROKE' ? (color ?? theme.colors.sub_gray11) : theme.colors.sub_gray1, + backgroundColor: type === 'FILL' ? (color ?? theme.colors.blue) : 'transparent', + borderColor: type === 'STROKE' ? (color ?? 'transparent') : 'transparent', + transform: `translate3d(${x ? '-50%' : '0'}, ${y ? '-50%' : '0'}, 0)`, + backdropFilter: type === 'STROKE' ? 'blur(10px)' : 'none', + width: y ? '100%' : 'auto', + }) as const, { - position: 'absolute', - boxSizing: 'border-box', - padding: '4px 12px', + padding: '2px 10px', - lineHeight: 1, + ...theme.font.body14Medium, whiteSpace: 'nowrap', - borderStyle: 'solid', borderWidth: '2px', [media[0]]: { - padding: '4px 8px', + padding: '0px 6px', + ...theme.font.detail12Medium, }, }, - ({ - x, - y, - color, - fillText, - fillRect, - strokeRect, - }: { - x?: number; - y?: number; - color?: themeColor; - background?: themeColor; - fillText?: true; - fillRect?: true; - strokeRect?: true; - }) => ({ - top: y ?? '0px', - left: x ?? '0px', - - width: typeof x == 'number' ? 'auto' : '100%', - - color: theme.colors[(fillText && color) ?? 'primary0'], - - background: theme.colors[strokeRect ? 'primary100' : ((fillRect && color) ?? 'transparent')], - borderColor: theme.colors[(strokeRect && color) ?? 'transparent'], - transform: - 'translate(' + (typeof x == 'number' ? '-50%' : '0') + ', ' + (typeof y == 'number' ? '-50%' : '0') + ')', - }), ); -export const ExtremeLabel = styled.span( - { - position: 'absolute', - - display: 'flex', - gap: '8px', - alignItems: 'center', - padding: '16px', - - lineHeight: '1', - whiteSpace: 'nowrap', - textAlign: 'center', - }, - ({ x, y, delta }: { x: number; y: number; delta?: boolean }) => ({ - top: y, - left: x, +export const deltaToChartColor = (delta: number) => { + return delta >= 0 ? '#EF4444' : '#2563EB'; +}; - flexDirection: delta ? 'column' : 'column-reverse', - - color: theme.colors[delta ? 'red' : 'blue'], - - transform: `translate(-50%, ${delta ? -100 : 0}%)`, +export const InfoHeader = styled.div({ + position: 'absolute', + top: '0', + left: '0', + right: '0', - svg: { - fill: theme.colors[delta ? 'red' : 'blue'], - }, - }), -); + display: 'flex', + flexDirection: 'column', + gap: '4px', + alignItems: 'start', + padding: '8px', -export const StockChartCanvasRefContainer = styled.canvas({ - position: 'absolute', - top: 0, - left: 0, + zIndex: 1, + userSelect: 'none', - width: '100%', - height: '100%', + overflow: 'hidden', }); -export const StockChartViewContainer = styled.div({ +export const InfoHeaderItemContainer = styled.div({ + ...theme.font.body14Semibold, display: 'flex', + gap: '4px', + whiteSpace: 'nowrap', - fontSize: '15px', + ['p']: { + margin: '0', + }, [media[0]]: { - fontSize: '11px', + ...theme.font.detail12Semibold, }, }); -export const StockChartItemContainer = styled.div( +export const InfoHeaderItemContent = styled.span( + ({ color }: { color?: string }) => ({ + color: color ?? theme.colors.sub_gray1, + }), { display: 'flex', - flexDirection: 'column', + gap: '4px', }, - ({ grow }: { grow?: true }) => - grow && { - flexGrow: 1, - - borderRight: `2px solid ${theme.colors.grayscale90}`, - }, ); -export const StockChartItemContent = styled.div( +export const InfoHeaderItemValueText = styled.p( + ({ color }: { color?: string }) => ({ + ['>b']: { + color: color ?? theme.colors.sub_gray1, + }, + }), { - position: 'relative', - - overflow: 'hidden', - - userSelect: 'none', - }, - ({ type }: { type?: 'price' | 'score' }) => ({ - height: !type ? 'auto' : type == 'price' ? '500px' : '200px', - - borderBottom: type ? `2px solid ${theme.colors.grayscale90}` : '', + display: 'flex', + gap: '4px', + color: theme.colors.sub_gray1, - [media[0]]: { - height: !type ? 'auto' : type == 'price' ? '300px' : '100px', + ['>b']: { + fontWeight: 'inherit', }, - }), + }, ); -export const StockChartItemCanvasContainer = styled.div({ - position: 'relative', +// ExtremePrice - display: 'flex', - flexDirection: 'column', +export const ExtremePriceContainer = styled.div({ + position: 'absolute', width: '100%', height: '100%', + top: '0', + left: '0', + overflow: 'hidden', }); -export const StockInfoDeltaLabel = styled.span(({ delta }: { delta?: number }) => ({ - color: theme.colors[!delta ? 'grayscale60' : delta > 0 ? 'red' : 'blue'], -})); +export const ExtremePriceLabel = styled.span( + ({ x, y, type }: { x: number; y: number; type: 'MAX' | 'MIN' }) => + ({ + top: y, + left: x, + flexDirection: type == 'MAX' ? 'column' : 'column-reverse', + color: deltaToChartColor(type == 'MAX' ? 1 : -1) ?? theme.colors.sub_gray10, + transform: `translate(-50%, ${type == 'MAX' ? -100 : 0}%)`, + ['>svg']: { + fill: deltaToChartColor(type == 'MAX' ? 1 : -1) ?? theme.colors.sub_gray10, + }, + }) as const, + { + position: 'absolute', -export const StockChartInfoHeaderItem = styled.div({ - display: 'flex', - gap: '4px', - width: 'auto', + display: 'flex', + gap: '4px', + alignItems: 'center', + padding: '8px 16px', - background: '#00000088', + ['>p']: { + ...theme.font.body14Semibold, + whiteSpace: 'nowrap', + margin: '0', + }, - userSelect: 'none', -}); + [media[0]]: { + ['>p']: { + ...theme.font.detail12Semibold, + }, + }, + }, +); -export const StockChartInfoHeader = styled.div({ - position: 'absolute', +// DateLabel +export const DateLabelContainer = styled.div({ + position: 'relative', + overflow: 'hidden', + userSelect: 'none', + flexShrink: '0', display: 'flex', - flexDirection: 'column', - gap: '8px', - alignItems: 'start', - padding: '8px', }); diff --git a/src/components/Search/StockChart/StockChart.tsx b/src/components/Search/StockChart/StockChart.tsx index 2009dad6..64c5201d 100644 --- a/src/components/Search/StockChart/StockChart.tsx +++ b/src/components/Search/StockChart/StockChart.tsx @@ -1,1172 +1,59 @@ -import { useEffect, useRef, useState } from 'react'; -import { - CHART_MOVING_AVERAGE_COLOR, - CHART_PRICE_FIELD, - CHART_SCALE_RATIO, - MAX_MIN, - PERIOD_CODE_TEXT, -} from '@ts/Constants'; -import { PERIOD_CODE, STOCK_COUNTRY } from '@ts/Types'; -import { drawLine, drawRect, setLineWidth } from '@utils/Canvas'; -import { formatDateISO, getDateLabel } from '@utils/Date'; -import { deltaColor } from '@utils/Delta'; -import { useIsMobile } from '@hooks/useIsMobile'; -import { useStockChartQuery } from '@controllers/query'; -import { theme, themeColor } from '@styles/themes'; -import DownSVG from '@assets/icons/down.svg?react'; -import UpSVG from '@assets/icons/up.svg?react'; -import { - ChartLabel, - ChartLabelBase, - ExtremeLabel, - StockChartCanvasRefContainer, - StockChartContainer, - StockChartHeader, - StockChartHeaderContents, - StockChartHeaderItem, - StockChartInfoHeader, - StockChartInfoHeaderItem, - StockChartItemCanvasContainer, - StockChartItemContainer, - StockChartItemContent, - StockChartViewContainer, - StockInfoDeltaLabel, -} from './StockChart.Style'; - -const DPR = window.devicePixelRatio; - -const useResizeObserver = (): [any, React.RefObject] => { - const ref = useRef(null); - const [size, setSize] = useState({ - width: 0, - height: 0, - }); - - useEffect(() => { - const element = ref.current; - if (!element) return; - - const resizeObserver = new ResizeObserver((entries) => { - for (let entry of entries) { - const { width, height } = entry.contentRect; - setSize({ - width, - height, - }); - } - }); - - resizeObserver.observe(element); - - return () => { - if (element) resizeObserver.unobserve(element); - resizeObserver.disconnect(); - }; - }, []); - - return [size, ref]; -}; - -const useManagedStateRef = (state: T) => { - const ref = useRef(); - - useEffect(() => { - ref.current = state; - }, [state]); - - return [ref]; -}; - -const useStateRef = (initializer: T): [T, (value: T) => void, React.MutableRefObject] => { - const [state, setState] = useState(initializer); - const ref = useRef(state); - - const setValue = (value: T) => { - setState((ref.current = value)); - }; - - return [state, setValue, ref]; -}; - -const formatPriceStr = (price: number, country: string, isCurrency?: boolean) => { - const currency = !isCurrency ? ' ' : country == 'KOREA' ? '원 ' : '$ '; - const digit = country == 'KOREA' || price >= 10000 ? 0 : price >= 1000 ? 1 : 2; - const priceStr = price?.toLocaleString(undefined, { - maximumFractionDigits: digit, - minimumFractionDigits: digit, - }); - - return `${priceStr}${currency}`; -}; - -const formatDeltaStr = (delta: number) => { - const symbol = !delta ? '' : delta > 0 ? '+' : '-'; - const deltaStr = Math.abs(delta * 100).toLocaleString(undefined, { - minimumFractionDigits: 1, - maximumFractionDigits: 2, - }); - - return `(${symbol + deltaStr}%) `; -}; - -const formatSymbol = (delta: number) => (!delta ? '' : delta > 0 ? '+' : '-'); -const formatVolume = (volume: number) => { - const unit = [ - { - type: 'B', - num: 1e9, - }, - { - type: 'M', - num: 1e6, - }, - { - type: 'K', - num: 1e3, - }, - { - type: '', - num: 1, - }, - ]; - const a = unit.find(({ num }) => volume >= num); - - return a && (volume / a.num).toFixed(2) + a.type; -}; - -const isBetween = (pos: number, min: number, max: number) => pos >= min && pos <= max; - -const getScaledValue = ({ max, min }: any, scale: number, height: number, gap: number) => { - const range = max - min; - - const scaled = Object.fromEntries( - Object.entries(MAX_MIN).map(([key, { mul }]) => { - return [key, min + range * (0.5 + mul * scale)]; - }), - ); - const scaledRange = scaled.max - scaled.min; - - const axisScale = - Array.from( - { - length: 12, - }, - (_, i) => Math.pow(10, i - 3), - ) - .flatMap((dec) => CHART_SCALE_RATIO.map((ratio) => dec * ratio)) - .find((scale) => (scale / scaledRange) * height >= gap) ?? 1e9; - - return { - ...scaled, - range: scaledRange, - axisScale: axisScale, - Y: (value: number) => height * (1 - (value - scaled.min) / scaledRange), - H: (value: number) => height * (value / scaledRange), - toValue: (y: number) => ((height - y) * scaledRange) / height + scaled.min, - }; -}; - -const getDateType = (prev: Date, curr: Date) => { - if (curr.getFullYear() != prev.getFullYear()) return 'Y'; - else if (curr.getMonth() != prev.getMonth()) return 'M'; - else return 'D'; -}; - -const StockChartView = ({ - chartData, - updateChart, - period, - country, -}: { - chartData: any[]; - updateChart: any; - period: PERIOD_CODE; - country: STOCK_COUNTRY; -}) => { - const isMobile = useIsMobile(); - - const initialBarSize = isMobile ? 4 : 8; - - const GRID_GAP = { - X: !isMobile ? 120 : 60, - PRICE_Y: !isMobile ? 40 : 30, - SCORE_Y: !isMobile ? 40 : 20, - }; - - const [chartContainerSize, chartContainerRef] = useResizeObserver(); - const [priceCanvasSize, priceCanvasRef] = useResizeObserver(); - const [scoreCanvasSize, scoreCanvasRef] = useResizeObserver(); - - const priceLabelRef = useRef(null); - const scoreLabelRef = useRef(null); - const [barSize, setBarSize, barSizeRef] = useStateRef(initialBarSize); - - const [canvasPos, setCanvasPos, canvasPosRef] = useStateRef({ - curr: { - x: 900, - }, - prev: { - x: 0, - }, - }); - const [isCanvasMove, setIsCanvasMove, isCanvasMoveRef] = useStateRef(false); - const [isMouseEnter, setIsMouseEnter] = useStateRef(false); - const [mousePos, setMousePos] = useStateRef({ - pos: { - x: 0, - y: 0, - }, - }); - - const [dateGrid, setDateGrid] = useState([]); - const [priceGrid, setPriceGrid] = useState([]); - const [scoreGrid, setScoreGrid] = useState([]); - - const [extremePrice, setExtremePrice] = useState(); - - const [recentPriceItem, setRecentPriceItem] = useState(); - const [lastPriceItem, setLastPriceItem] = useState(); - - const [scaledPrice, setScaledPrice] = useState(); - const [scaledScore, setScaledScore] = useState(); - const [, setScaledVolume] = useState(); - - const [priceScale, setPriceScale, priceScaleRef] = useStateRef(4 / 5); - const [scoreScale, setScoreScale, scoreScaleRef] = useStateRef(3 / 5); - const [volumeScale, setVolumeScale] = useState(4 / 5); - - const [, setIsZoom, isZoomRef] = useStateRef(false); - const [, setZoom, zoomRef] = useStateRef(0); - - const [mousePosInfo, setMousePosInfo] = useState(); - - const [stateRef] = useManagedStateRef({ - chartLength: chartData.length, - }); - - const barGap = 1.5; - - const itemWidth = barSize * barGap; - - useEffect(() => { - const { width, height } = priceCanvasSize; - const priceCanvas = priceCanvasRef.current; - if (!priceCanvas) return; - priceCanvas.width = width; - priceCanvas.height = height; - }, [priceCanvasSize]); - - useEffect(() => { - const canvasContainer = chartContainerRef.current; - if (!canvasContainer) return; - - const { width } = canvasContainer.getBoundingClientRect(); - - setCanvasPos({ - curr: { - x: width - GRID_GAP.X, - }, - prev: { - x: 0, - }, - }); - setBarSize(initialBarSize); - }, [period]); - - useEffect(() => { - if (!chartData.length) return; - const width = chartContainerSize.width; - const priceHeight = priceCanvasSize.height; - const scoreHeight = scoreCanvasSize.height; - - const dateList = getDateList(width); - const dateGrid = getDateGrid(dateList); - setDateGrid(dateGrid.filter(({ posX }) => isBetween(posX, 0, width))); - - if (dateList[0].posX >= 0) updateChart(dateList[0].dateISO); - - const currX = canvasPos.curr.x; - - const chartItems = chartData - .map((e, i) => { - const posX = currX - (chartData.length - i - 1) * itemWidth; - if (!isBetween(posX, -itemWidth, width + itemWidth)) return; - - return { - ...e, - pos: { - x: posX, - }, - }; - }) - .filter((e) => e); - - const rangedPrice = Object.fromEntries( - Object.entries(MAX_MIN).map(([key, { type, init }]) => [ - key, - chartItems.reduce((prev, { price, SMA }) => { - return Math[key as 'max' | 'min']( - prev, - Object.values(SMA).reduce((prev: number, { price }: any) => { - return Math[key as 'max' | 'min'](prev, price); - }, price[type].value), - ); - }, init), - ]), - ); - - const scaledPrice: any = getScaledValue(rangedPrice, priceScale, priceHeight, GRID_GAP.PRICE_Y); - setScaledPrice(scaledPrice); - - const priceGrid = getGrid(scaledPrice, (value: any) => formatPriceStr(value, country)); - setPriceGrid(priceGrid); - - const chartPriceItems = getChartPriceItems(chartItems, scaledPrice); - const chartSMAItems = getChartSMAItems(chartItems, scaledPrice); - - // recentPrice - const recentPrice = chartData[chartData.length - 1].price; - - const recentPriceItem = getPriceItem(recentPrice, scaledPrice); - setRecentPriceItem(recentPriceItem); - - // lastPrice - const lastPrice = chartItems[chartItems.length - 1]?.price; - - const lastPriceItem = getPriceItem(lastPrice, scaledPrice); - setLastPriceItem(lastPriceItem); - - // extremePrice - - const extremePrice = getExtremePrice(chartItems, recentPrice, [0, width], scaledPrice); - setExtremePrice(extremePrice); - - // score - - const rangedScore = { - max: 100, - min: 0, - }; - - const scaledScore: any = getScaledValue(rangedScore, scoreScale, scoreHeight, GRID_GAP.SCORE_Y); - setScaledScore(scaledScore); - - const rangedVolume = { - min: 0, - max: chartItems.reduce((prev, { trading: { volume } }) => Math.max(prev, volume), 0), - }; - - const scaledVolume: any = getScaledValue(rangedVolume, volumeScale, scoreHeight, GRID_GAP.SCORE_Y); - setScaledVolume(scaledVolume); - - const scoreGrid = getGrid(scaledScore, (value: any) => (value < 0 || value > 100 ? '' : value)); - setScoreGrid(scoreGrid); - - const scoreChartList = getChartScoreItems(chartItems, scaledScore, scaledVolume); - - drawPriceChart(dateGrid, chartPriceItems, chartSMAItems, recentPriceItem); - drawScoreChart(dateGrid, scoreChartList); - }, [chartData, priceCanvasSize, canvasPos, barSize, priceScale, scoreScale]); - - useEffect(() => { - const { width, height } = chartContainerSize; - - const chartContainer = chartContainerRef.current; - if (!chartContainer) return; - chartContainer.width = width * DPR; - chartContainer.height = height * DPR; - const ctx = chartContainer.getContext('2d'); - if (!ctx) return; - - ctx.clearRect(0, 0, width, height); - if (!isMouseEnter) { - return; - } - - const { pos } = mousePos; - const { curr } = canvasPos; - - const alignedPos = { - x: curr.x - Math.floor((curr.x - pos.x + itemWidth / 2) / itemWidth) * itemWidth, - y: pos.y, - }; - - if (!chartData.length) return; - const dateList = getDateList(width); - const dateStr = dateList.find((e) => e.posX == alignedPos.x)?.dateISO; - const priceStr = formatPriceStr(scaledPrice.toValue(alignedPos.y), country); - const scoreStr = ~~scaledScore.toValue(alignedPos.y - priceCanvasSize.height); - const selectedPrice = chartData.find(({ date }) => dateStr == formatDateISO(date)); - - setMousePosInfo({ - pos: alignedPos, - dateStr, - priceStr, - scoreStr, - price: selectedPrice?.price, - SMA: selectedPrice?.SMA, - score: selectedPrice?.score, - trading: selectedPrice?.trading, - }); - - ctx.strokeStyle = theme.colors.primary0; - setLineWidth(ctx, 1); - ctx.setLineDash([4, 2]); - drawLine(ctx, [ - [alignedPos.x, 0], - [alignedPos.x, height], - ]); - - drawLine(ctx, [ - [0, alignedPos.y], - [width, alignedPos.y], - ]); - }, [isMouseEnter, mousePos, canvasPos, barSize, scaledPrice]); - - useEffect(() => { - if (isMobile) { - document.documentElement.style.overflow = isCanvasMove ? 'hidden' : ''; - document.documentElement.style.overscrollBehavior = isCanvasMove ? 'contain' : ''; - } - }, [isCanvasMove]); - - useEffect(() => { - const charContainer = chartContainerRef.current; - if (!charContainer) return; - const priceLabel = priceLabelRef.current; - if (!priceLabel) return; - const scoreLabel = scoreLabelRef.current; - if (!scoreLabel) return; - - if (!isMobile) { - charContainer.addEventListener('mousedown', handleCanvasPointerDown); - window.addEventListener('mousemove', handleCanvasPointerMove); - window.addEventListener('mouseup', handleCanvasPointerUp); - - charContainer.addEventListener('wheel', handleCanvasZoom, { passive: false }); - priceLabel.addEventListener('wheel', handlePriceZoom, { passive: false }); - scoreLabel.addEventListener('wheel', handleScoreZoom, { passive: false }); - - charContainer.addEventListener('mouseenter', handleCanvasMouseEnter); - charContainer.addEventListener('mouseleave', handleCanvasMouseLeave); - charContainer.addEventListener('mousemove', handlePriceCanvasMouseMove); - } else { - charContainer.addEventListener('touchstart', handleCanvasPointerDown); - window.addEventListener('touchmove', handleCanvasPointerMove, { - passive: false, - }); - window.addEventListener('touchend', handleCanvasPointerUp); - } - - charContainer.addEventListener('dragstart', handleBlockDrag); - - return () => { - if (!isMobile) { - charContainer.removeEventListener('mousedown', handleCanvasPointerDown); - window.removeEventListener('mousemove', handleCanvasPointerMove); - window.removeEventListener('mouseup', handleCanvasPointerUp); - - charContainer.removeEventListener('wheel', handleCanvasZoom); - priceLabel.removeEventListener('wheel', handlePriceZoom); - scoreLabel.removeEventListener('wheel', handleScoreZoom); - - charContainer.removeEventListener('mouseenter', handleCanvasMouseEnter); - charContainer.removeEventListener('mouseleave', handleCanvasMouseLeave); - charContainer.removeEventListener('mousemove', handlePriceCanvasMouseMove); - } else { - charContainer.removeEventListener('touchstart', handleCanvasPointerDown); - window.removeEventListener('touchmove', handleCanvasPointerMove); - window.removeEventListener('touchend', handleCanvasPointerUp); - } - - charContainer.removeEventListener('dragstart', handleBlockDrag); - }; - }, []); - - const getDateList = (width: number) => { - const currX = canvasPos.curr.x; - - const dateList: any[] = chartData.map(({ date }, i, arr) => { - const type = i ? getDateType(arr[i - 1].date, date) : 'D'; - return { - type: type, - date, - posX: currX - (chartData.length - i - 1) * itemWidth, - dateISO: formatDateISO(date), - dateStr: getDateLabel(date, type), - }; - }); - - const nowDate = new Date(chartData[chartData.length - 1].date); - for (let i = 1; i <= width / itemWidth; i++) { - if (period == 'D') { - if (nowDate.getDay() == 5) { - nowDate.setDate(nowDate.getDate() + 3); - } else { - nowDate.setDate(nowDate.getDate() + 1); - } - } else if (period == 'W') { - nowDate.setDate(nowDate.getDate() + 7); - } else if (period == 'M') { - nowDate.setMonth(nowDate.getMonth() + 2, 0); - } else if (period == 'Y') { - nowDate.setFullYear(nowDate.getFullYear() + 2, 0, 0); - } - - const type = getDateType(dateList[dateList.length - 1].date, nowDate); - - dateList.push({ - type: type, - date: new Date(nowDate), - posX: currX + i * itemWidth, - dateISO: formatDateISO(nowDate), - dateStr: getDateLabel(nowDate, type), - }); - } - return dateList; - }; - - const getDateGrid = (dateList: any[]) => { - let dayWidth = 0; - let beforeType = 'D'; - - const dateGrid = dateList.reduce((acc: any[], e) => { - const { type } = e; - dayWidth += itemWidth; - - if (dayWidth < GRID_GAP.X) { - if ((type === 'M' && beforeType === 'D') || (type === 'Y' && ['D', 'M'].includes(beforeType))) acc.pop(); - else return acc; - } - - acc.push(e); - - dayWidth = 0; - beforeType = type; - return acc; - }, []); - - return dateGrid; - }; - - const getChartPriceItems = (chartItems: any[], scaledPrice: any) => { - return chartItems.map(({ pos, price }) => { - const { open, close, high, low } = Object.fromEntries( - ['open', 'close', 'high', 'low'].map((e) => [e, price[e].value]), - ); - - return { - pos: pos, - market: { - y: scaledPrice.Y(Math.max(open, close)), - h: scaledPrice.H(Math.abs(open - close)), - }, - daily: { - y: scaledPrice.Y(high), - h: scaledPrice.H(high - low), - }, - delta: close >= open, - }; - }); - }; - - const getChartSMAItems = (chartItems: any[], scaledPrice: any) => { - const chartSMAItems: Record< - string, - { - color: themeColor; - items: { - pos: { - x: number; - y: number; - }; - }[]; - } - > = Object.fromEntries( - Object.entries(CHART_MOVING_AVERAGE_COLOR).map(([key, value]) => [ - key, - { - color: value, - items: [], - }, - ]), - ); - - chartItems.forEach(({ pos, SMA }) => { - Object.entries(SMA).forEach(([key, { price }]: [string, any]) => { - chartSMAItems[key].items.push({ - pos: { - x: pos.x, - y: scaledPrice.Y(price), - }, - }); - }); - }); - - return chartSMAItems; - }; - - const getPriceItem = (price: any, scaledPrice: any) => { - if (!price) return; - - const { open, close } = Object.fromEntries(['open', 'close'].map((e) => [e, price[e].value])); - - return { - pos: { - x: 0, - y: scaledPrice.Y(close), - }, - delta: close - open, - priceStr: formatPriceStr(close, country), - }; - }; - - const getExtremePrice = (chartItems: any[], recentPrice: any, [min, max]: [number, number], scaledPrice: any) => { - const extremePriceList = chartItems.filter(({ pos }) => isBetween(pos.x, min, max)); - if (!extremePriceList.length) return; - - return Object.fromEntries( - Object.entries(MAX_MIN).map(([key, { type, label }]: [string, any]) => { - const { - pos, - price: { - [type]: { value }, - }, - } = extremePriceList.reduce((prev, curr) => { - const prevValue = prev.price[type].value; - const currValue = curr.price[type].value; - - return Math[key as 'max' | 'min'](prevValue, currValue) === currValue ? curr : prev; - }); - return [ - key, - { - pos: { - x: pos.x, - y: scaledPrice.Y(value), - }, - price: { - value: value, - delta: recentPrice.close.value / value - 1, - }, - label, - }, - ]; - }), - ); - }; - - const getGrid = (scaledValue: any, valueStr: any) => { - return Array.from( - { - length: Math.ceil(scaledValue.range / scaledValue.axisScale) + 1, - }, - (_, i) => { - const value = (i + Math.floor(scaledValue.min / scaledValue.axisScale)) * scaledValue.axisScale; - return { - valueStr: valueStr(value), - pos: { - x: 0, - y: scaledValue.Y(value), - }, - }; - }, - ); - }; - - const getChartScoreItems = (chartItems: any[], scaledScore: any, scaledVolume: any) => { - return chartItems.map(({ pos, score, trading: { volume, delta } }) => ({ - pos: pos, - score: { - ...score, - y: scaledScore.Y(score.value), - }, - trading: { - y: scaledVolume.Y(volume), - h: scaledVolume.H(volume), - delta: delta, - }, - barSize: barSize, - })); - }; - - const drawPriceChart = ( - dateGrid: any[], - chartPriceItems: any[], - chartSMAItems: Record< - string, - { - color: themeColor; - items: { - pos: { - x: number; - y: number; - }; - }[]; - } - >, - recentPriceItem: any, - ) => { - const { width, height } = priceCanvasSize; - const canvas = priceCanvasRef.current; - if (!canvas) return; - canvas.width = width * DPR; - canvas.height = height * DPR; - const ctx = canvas.getContext('2d'); - if (!ctx) return; - ctx.clearRect(0, 0, width, height); - - // grid - - setLineWidth(ctx, 1); - ctx.strokeStyle = theme.colors.grayscale90; - - dateGrid.forEach(({ posX }) => { - drawLine(ctx, [ - [posX, 0], - [posX, height], - ]); - }); - - priceGrid.forEach(({ pos }) => { - drawLine(ctx, [ - [0, pos.y], - [width, pos.y], - ]); - }); - - // priceItems - - setLineWidth(ctx, 1); - chartPriceItems.forEach(({ delta, pos, market, daily }: any) => { - ctx.fillStyle = delta ? theme.colors.red : theme.colors.blue; - ctx.strokeStyle = delta ? theme.colors.red : theme.colors.blue; - drawRect(ctx, pos.x - barSize / 2, market.y, barSize, market.h); - drawLine(ctx, [ - [pos.x, daily.y], - [pos.x, daily.y + daily.h], - ]); - }); - - setLineWidth(ctx, 2); - Object.values(chartSMAItems).map(({ color, items }: { color: themeColor; items: any[] }) => { - ctx.strokeStyle = theme.colors[color]; - - drawLine( - ctx, - items.map((e) => [e.pos.x, e.pos.y]), - ); - }); - - ctx.strokeStyle = theme.colors['red']; - setLineWidth(ctx, 1); - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; - ctx.setLineDash([4, 4]); - drawLine(ctx, [ - [0, recentPriceItem.pos.y], - [width, recentPriceItem.pos.y], - ]); - }; - - const drawScoreChart = (dateGrid: any[], scoreChartList: any[]) => { - const { width, height } = scoreCanvasSize; - const canvas = scoreCanvasRef.current; - if (!canvas) return; - canvas.width = width * DPR; - canvas.height = height * DPR; - const ctx = canvas.getContext('2d'); - if (!ctx) return; - ctx.clearRect(0, 0, width, height); - - // grid - - setLineWidth(ctx, 1); - ctx.strokeStyle = theme.colors.grayscale90; - - dateGrid.forEach(({ posX }) => { - drawLine(ctx, [ - [posX, 0], - [posX, height], - ]); - }); - - scoreGrid.forEach(({ pos }) => { - drawLine(ctx, [ - [0, pos.y], - [width, pos.y], - ]); - }); - - ctx.globalAlpha = 0.5; - scoreChartList.map(({ pos, trading, barSize }: any) => { - ctx.fillStyle = theme.colors[deltaColor(trading.delta)]; - ctx.strokeStyle = theme.colors[deltaColor(trading.delta)]; - - drawRect(ctx, pos.x - barSize / 2, trading.y, barSize, trading.h); - }); - - ctx.globalAlpha = 1; - setLineWidth(ctx, 2); - ctx.strokeStyle = theme.colors.cyan; - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; - - drawLine( - ctx, - scoreChartList - .map((e: any) => { - if (!e.score.value) return; - return [e.pos.x, e.score.y]; - }) - .filter((e: any) => e), - ); - }; - - const zoomChart = (delta: number, x: number) => { - const barSize = barSizeRef.current; - const scaledBarSize = barSize * delta; - if (scaledBarSize <= 0.25 || scaledBarSize > 250) return; - - const width = chartContainerRef.current?.offsetWidth ?? 0; - const itemWidth = scaledBarSize * barGap; - - const MinX = itemWidth * (3 / 2); - const MaxX = width + itemWidth * (stateRef.current.chartLength - 5 / 2); - const canvasX = x + (canvasPosRef.current.curr.x - x) * (scaledBarSize / barSize); - - const currX = canvasX < MinX ? MinX : canvasX > MaxX ? MaxX : canvasX; - - setBarSize(scaledBarSize); - setCanvasPos({ - ...canvasPosRef.current, - curr: { - x: currX, - }, - }); - }; - - const handleCanvasPointerDown = (e: MouseEvent | TouchEvent) => { - const ponterX = 'clientX' in e ? e.clientX : e.touches[0].clientX; - setIsCanvasMove(true); - setCanvasPos({ - ...canvasPosRef.current, - prev: { - x: ponterX, - }, - }); - setIsZoom(!('clientX' in e) && e.touches.length >= 2); - if (!('clientX' in e) && Object.values(e.touches).length >= 2) { - const touches = Object.values(e.touches); - const now = { - x: 0, - y: 0, - a: 0, - }; - touches.map((e) => { - now.x += e.clientX / touches.length; - now.y += e.clientY / touches.length; - }); - touches.map((e) => { - now.a += Math.sqrt(Math.pow(e.clientX - now.x, 2) + Math.pow(e.clientY - now.y, 2)) / touches.length; - }); - setZoom(now.a); - } - }; - - const handleCanvasPointerMove = (e: MouseEvent | TouchEvent) => { - const pointerX = 'clientX' in e ? e.clientX : e.touches[0].clientX; - if (isZoomRef.current && !('clientX' in e)) { - if (e.cancelable) e.preventDefault(); - const touches = Object.values(e.touches); - const now = { - x: 0, - y: 0, - a: 0, - }; - touches.map((e) => { - now.x += e.clientX / touches.length; - now.y += e.clientY / touches.length; - }); - touches.map((e) => { - now.a += Math.sqrt(Math.pow(e.clientX - now.x, 2) + Math.pow(e.clientY - now.y, 2)) / touches.length; - }); - if (zoomRef.current && Math.abs(zoomRef.current - now.a) > 10) { - zoomChart(now.a / zoomRef.current, now.x); - setZoom(now.a); - } - } else if (isCanvasMoveRef.current) { - const deltaX = pointerX - canvasPosRef.current.prev.x; - const barSize = barSizeRef.current; - - const width = chartContainerRef.current?.offsetWidth ?? 0; - const itemWidth = barSize * barGap; - - const MinX = itemWidth * (3 / 2); - const MaxX = width + itemWidth * (stateRef.current.chartLength - 5 / 2); - const canvasX = canvasPosRef.current.curr.x + deltaX; - - const currX = canvasX < MinX ? MinX : canvasX > MaxX ? MaxX : canvasX; - - setCanvasPos({ - curr: { - x: currX, - }, - prev: { - x: pointerX, - }, - }); - } - }; - - const handleCanvasPointerUp = (e: MouseEvent | TouchEvent) => { - setIsCanvasMove(false); - setIsZoom(!('clientX' in e) && e.touches.length >= 2); - }; - - const handleCanvasZoom = (e: WheelEvent) => { - e.preventDefault(); - const deltaY = e.deltaY > 10 ? 10 : e.deltaY < -10 ? -10 : e.deltaY; - zoomChart(1 - deltaY / 100, e.offsetX); - }; - - const handleCanvasMouseEnter = () => { - setIsMouseEnter(true); - }; - - const handleCanvasMouseLeave = () => { - setIsMouseEnter(false); - setMousePosInfo(null); - }; - - const handlePriceCanvasMouseMove = (e: MouseEvent) => { - setMousePos({ - pos: { - x: e.offsetX, - y: e.offsetY, - }, - }); - }; - - const handlePriceZoom = (e: WheelEvent) => { - e.preventDefault(); - const deltaY = e.deltaY > 10 ? 10 : e.deltaY < -10 ? -10 : e.deltaY; - const scale = (1 + deltaY / 100) * priceScaleRef.current; - if (scale <= 0.1 || scale > 20) return; - setPriceScale(scale); - }; - - const handleScoreZoom = (e: WheelEvent) => { - e.preventDefault(); - const deltaY = e.deltaY > 10 ? 10 : e.deltaY < -10 ? -10 : e.deltaY; - const scale = (1 + deltaY / 100) * scoreScaleRef.current; - if (scale <= 0.1 || scale > 20) return; - setScoreScale(scale); - setVolumeScale(scale); - }; - - const handleBlockDrag = () => false; - - return ( - - - - - - {extremePrice && - Object.entries(extremePrice).map(([key, value]: [string, any]) => ( - - {`${value.label} : ${formatPriceStr(value.price.value, country, true)} ${formatDeltaStr(value.price.delta)}`} - {key == 'max' ? : } - - ))} - - {!isMobile && ( - - {Object.entries(CHART_PRICE_FIELD).map(([key, value]: [string, any]) => ( - - ))} - - )} - - 이동평균선 - {Object.entries(CHART_MOVING_AVERAGE_COLOR).map(([range, color]) => ( - - ))} - - - - - - - - - - - - - - - 0000 - {dateGrid.map((e: any) => ( - - {e.dateStr} - - ))} - {mousePosInfo?.dateStr && ( - - {mousePosInfo.dateStr} - - )} - - - - - {priceGrid[priceGrid.length - 1]?.priceStr} - {priceGrid.map((e: any) => ( - - {e.valueStr} - - ))} - {lastPriceItem && ( - - {lastPriceItem.priceStr} - - )} - {recentPriceItem && ( - - {recentPriceItem.priceStr} - - )} - {mousePosInfo?.priceStr && ( - - {mousePosInfo.priceStr} - - )} - - - 100 - {scoreGrid.map( - (e: any) => - e.valueStr !== '' && ( - - {e.valueStr} - - ), - )} - {mousePosInfo?.scoreStr && ( - - {mousePosInfo.scoreStr} - - )} - - - - ); -}; - -const ChartBottomInfo = ({ trading, score, isMobile }: { trading: any; score: any; isMobile: boolean }) => { - return ( - <> - 거래량{' '} - {!isMobile && trading && ( - <> - {formatVolume(trading.volume)}{' '} - {formatDeltaStr(trading.delta)} - - )} - / 인간지표{' '} - {!isMobile && score?.value && ( - <> - {score.value}점{' '} - - ({formatSymbol(score.delta) + Math.abs(score.delta)}점) - - - )} - - ); -}; - -const ChartPriceInfo = ({ label, price, country }: { label: string; price: any; country: string }) => { - return ( - <> - {label + ' '} - {typeof price?.value == 'number' ? formatPriceStr(price.value, country, true) : ''} - - {typeof price?.delta == 'number' ? formatDeltaStr(price?.delta) : ''} - - - ); -}; - -const ChartSMAInfo = ({ - range, - price, - country, - color, -}: { - range: number; - price?: number; - country: string; - color: themeColor; -}) => { - return ( - <> - - {range + ' '} - - {price && formatPriceStr(price, country, true)} - - ); -}; +import React, { useState } from 'react'; +import { PERIOD_CODE_TEXT } from '@ts/Constants'; +import { StockCountryKey } from '@ts/StockCountry'; +import { PERIOD_CODE } from '@ts/Types'; +import { useStockChartQuery } from '@controllers/stocks/query'; +import StockChartView from './ChartView'; +import { ChartContainer, ChartHeader, ChartHeaderContents, ChartHeaderItem } from './StockChart.Style'; const StockChart = ({ stockId, symbolName, country, + chartHeight, + chartInteractive, }: { stockId: number; symbolName: string; - country: STOCK_COUNTRY; + country: StockCountryKey; + chartHeight?: { price: string; score: string }; + chartInteractive?: boolean; }) => { const [selectedPeriod, setSelectedPeriod] = useState('D'); const [chartData, updateChartData] = useStockChartQuery(stockId, selectedPeriod); + const handlePeriodClick = (period: PERIOD_CODE) => (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setSelectedPeriod(period); + }; + return ( - - - {symbolName} - + + + {symbolName} + {(Object.entries(PERIOD_CODE_TEXT) as [PERIOD_CODE, string][]).map(([key, value], i) => ( - setSelectedPeriod(key)} + onPointerDown={handlePeriodClick(key)} > {value} - + ))} - - - - + + + + ); }; diff --git a/src/components/Search/StockWordCloud/StockWordCloud.tsx b/src/components/Search/StockWordCloud/StockWordCloud.tsx index 594958b3..60e5582e 100644 --- a/src/components/Search/StockWordCloud/StockWordCloud.tsx +++ b/src/components/Search/StockWordCloud/StockWordCloud.tsx @@ -1,10 +1,9 @@ import { useEffect, useRef, useState } from 'react'; -import { useLocation } from 'react-router-dom'; import { WordCloudItem } from '@ts/Interfaces'; -import { STOCK_COUNTRY } from '@ts/Types'; +import { StockCountryKey } from '@ts/StockCountry'; import { useIsMobile } from '@hooks/useIsMobile'; import LoadingComponent from '@components/Common/LoadingComponent'; -import { useWordCloudQuery } from '@controllers/query'; +import { useWordCloudQuery } from '@controllers/stocks/query'; import { StockWordCloudContainer, Word, WordCloudTestText, WordContainer } from './StockWordCloud.Style'; const StockWordCloudContents = ({ @@ -32,8 +31,7 @@ const StockWordCloudContents = ({ ); }; -const StockWordCloud = ({ symbol, country }: { symbol: string; country: STOCK_COUNTRY }) => { - const { state } = useLocation(); +const StockWordCloud = ({ symbol, country }: { symbol: string; country: StockCountryKey }) => { const isMobile = useIsMobile(); const containerRef = useRef(null); @@ -52,9 +50,8 @@ const StockWordCloud = ({ symbol, country }: { symbol: string; country: STOCK_CO const testTextRef = useRef(null); useEffect(() => { - if (symbol == state?.symbol) return; setCurrentIndex(-1); - }, [state]); + }, [symbol, country]); useEffect(() => { if (!wordCloud || currentIndex > wordCloud.length) return; diff --git a/src/components/SearchBar/SearchBar.Style.ts b/src/components/SearchBar/SearchBar.Style.ts deleted file mode 100644 index 0ae8a107..00000000 --- a/src/components/SearchBar/SearchBar.Style.ts +++ /dev/null @@ -1,428 +0,0 @@ -// SearchBar -import styled from '@emotion/styled'; -import { media, theme } from '@styles/themes'; - -export const SearchBarLayout = styled.div({ - padding: '0 60px 80px', - [media[0]]: { - position: 'relative', - padding: '0 20px 40px', - }, -}); - -export const SearchBarContainer = styled.div( - { - display: 'flex', - padding: '12px', - gap: '12px', - fontWeight: '700', - color: theme.colors.primary5, - lineHeight: '1', - background: theme.colors.primary100, - outline: 'none', - position: 'relative', - - [media[0]]: { - position: 'static', - gap: '12px', - padding: '12px 12px', - }, - }, - (props: { active: boolean }) => ({ - borderRadius: !props.active ? '12px' : '12px 12px 0 0', - [media[0]]: { - borderRadius: '8px', - }, - }), -); - -export const SearchBarInput = styled.div({ - display: 'flex', - alignItems: 'center', - background: theme.colors.grayscale100, - padding: '18px', - borderRadius: '8px', - width: '100%', - fontSize: '24px', - ['input']: { - boxSizing: 'border-box', - width: '100%', - border: 'none', - background: theme.colors.transparent, - color: theme.colors.primary0, - outline: 'none', - fontFamily: 'Pretendard', - - '::placeholder': { - color: theme.colors.grayscale50, - }, - }, - ['svg']: { - height: '1em', - width: 'auto', - stroke: theme.colors.grayscale30, - cursor: 'pointer', - }, - [media[0]]: { - padding: '0 12px', - borderRadius: '4px', - fontSize: '18px', - ['input']: { - padding: '12px 0', - }, - }, -}); - -export const SearchBarSelectBox = styled.div( - { - position: 'relative', - display: 'flex', - width: '50%', - - ['label']: { - cursor: 'pointer', - fontSize: '17px', - borderRadius: '8px', - padding: '18px', - alignContent: 'center', - width: '100%', - background: theme.colors.grayscale100, - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - ['span']: { - transition: 'all 0.25s ease-in-out', - }, - ['svg']: { - fill: theme.colors.grayscale10, - height: '1em', - width: '1em', - }, - }, - [media[0]]: { - width: 'auto', - ['label']: { - ['span']: { - width: '75px', - overflow: 'hidden', - whiteSpace: 'nowrap', - }, - fontSize: '15px', - padding: '12px', - borderRadius: '4px', - }, - }, - }, - (props: { focus: boolean; minimize?: boolean }) => ({ - ...(props.focus && { - ['label']: { - background: theme.colors.grayscale90, - borderRadius: '8px 8px 0 0', - [media[0]]: { - borderRadius: '4px 4px 0 0', - }, - }, - ul: { - maxHeight: '150px', - }, - }), - ...(props.minimize && { - [media[0]]: { - ['label']: { - ['span']: { - width: '0px', - }, - }, - }, - }), - }), -); - -export const SearchBarSelectBoxItems = styled.ul( - { - cursor: 'pointer', - position: 'absolute', - zIndex: '20', - top: '100%', - listStyle: 'none', - padding: '0', - margin: '0', - width: '100%', - display: 'flex', - maxHeight: '0', - flexDirection: 'column', - alignItems: 'center', - borderRadius: '0 0 8px 8px', - overflow: 'hidden', - transition: 'max-height .25s ease-in-out', - - li: { - width: '100%', - background: theme.colors.grayscale100, - textAlign: 'center', - fontSize: '15px', - padding: '18px 0', - borderTop: `2px solid ${theme.colors.grayscale100}`, - }, - - [media[0]]: { - borderRadius: '0 0 4px 4px', - li: { - fontSize: '13px', - padding: '12px 0', - }, - }, - }, - (props: { select: number }) => ({ - [`li:nth-of-type(${props.select + 1})`]: { - background: theme.colors.grayscale90, - }, - }), -); - -// SearchBarResult - -export const SearchBarResultLayoutContainer = styled.div( - { - position: 'absolute', - zIndex: '10', - left: '0', - right: '0', - top: '100%', - overflow: 'hidden', - height: 'auto', - - transition: 'max-height .25s ease-in-out', - background: theme.colors.primary100, - - [media[0]]: { - overflow: 'scroll', - height: '100vh', - }, - }, - (props: { height: number }) => ({ - maxHeight: props.height, - }), -); - -export const SearchBarResultLayout = styled.div({ - boxSizing: 'border-box', - width: '100%', - padding: '32px', - - [media[0]]: { - padding: '32px 20px', - }, -}); - -export const SearchBarResultContainer = styled.div({ - display: 'flex', - gap: '24px', - width: '100%', - height: '100%', - - [media[0]]: { - flexDirection: 'column', - }, -}); - -export const SearchBarResultContent = styled.div( - { - display: 'flex', - flexDirection: 'column', - gap: '18px', - boxSizing: 'content-box', - height: '100%', - textTransform: 'uppercase', - - [media[0]]: { - gap: '12px', - width: '100%', - }, - }, - (props: { width: string }) => ({ - width: props.width, - }), -); - -export const SearchBarResultSVG = styled.div({ - width: '100%', - height: '100%', - boxSizing: 'border-box', - padding: '72px', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - ['svg']: { - boxSizing: 'content-box', - minHeight: '260px', - width: 'auto', - height: '100%', - }, - - [media[0]]: { - padding: '32px', - }, -}); - -// SearchBarResultTitle - -export const SearchBarResultTitle = styled.div({ - fontSize: '24px', - display: 'flex', - alignItems: 'center', - gap: '12px', - - [media[0]]: { - fontSize: '21px', - gap: '8px', - }, -}); - -export const SearchBarResultSubtitle = styled.span({ - color: theme.colors.grayscale30, - fontSize: '15px', - whiteSpace: 'nowrap', - fontWeight: '500', - - ['b']: { - fontWeight: '900', - }, - [media[0]]: { - fontSize: '13px', - }, -}); - -// SearchBarResultItem - -export const SearchBarResultGridContainer = styled.div( - { - display: 'grid', - gridAutoFlow: 'column', - columnGap: '32px', - - ['> div']: { - padding: '8px 0', - borderBottom: `1px solid ${theme.colors.grayscale80}`, - overflow: 'hidden', - - ':last-child, :nth-of-type(5n)': { - border: 'none', - }, - }, - - [media[0]]: { - display: 'flex', - flexDirection: 'column', - ['> div:nth-of-type(5n):not(:last-child)']: { - borderBottom: `1px solid ${theme.colors.grayscale80}`, - }, - }, - }, - (props: { column: number }) => ({ - gridTemplateColumns: `repeat(${props.column}, 1fr)`, - gridTemplateRows: `repeat(5, ${props.column}fr)`, - }), -); - -export const SearchBarResultItemContainer = styled.div({ - display: 'flex', - alignItems: 'center', - gap: '12px', - padding: '9px 12px 9px 18px', - borderRadius: '0 32px 32px 0', - cursor: 'pointer', - fontSize: '18px', - - [':hover']: { - background: theme.colors.grayscale100, - ['svg']: { - stroke: theme.colors.primary5, - }, - }, - - [media[0]]: { - fontSize: '15px', - padding: '6px 9px 6px 12px', - // height: '18px', - gap: '8px', - }, -}); - -export const SearchBarResultItemSVG = styled.div({ - lineHeight: 1, - height: 'auto', - display: 'flex', - alignItems: 'center', - ['> svg']: { - width: '.9em', - height: '.9em', - cursor: 'pointer', - borderRadius: '24px', - [':hover']: { - background: theme.colors.grayscale70, - }, - }, - [media[0]]: { - ['> svg']: { - stroke: theme.colors.primary5, - }, - }, -}); - -export const SearchBarResultItemKeyword = styled.span( - { - padding: '8px 16px', - fontSize: '15px', - textWrap: 'nowrap', - borderRadius: '24px', - [media[0]]: { - padding: '6px 12px', - fontSize: '13px', - }, - }, - (props: { matched: boolean }) => ({ - background: props.matched ? theme.colors.primary50 : theme.colors.grayscale80, - }), -); - -export const SearchBarResultItemSubtitle = styled.span({ - color: theme.colors.grayscale40, - fontSize: '15px', - whiteSpace: 'nowrap', - [media[0]]: { - fontSize: '11px', - }, -}); - -export const SearchBarResultItemTitle = styled.span({ - color: theme.colors.primary0, - textOverflow: 'ellipsis', - overflow: 'hidden', - - fontSize: '19px', - marginRight: 'auto', - whiteSpace: 'nowrap', - ['span']: { - color: theme.colors.primary40, - }, - - ['> div']: { - display: 'flex', - gap: '8px', - fontSize: '15px', - paddingTop: '8px', - ['> div']: { - color: theme.colors.grayscale60, - }, - }, - - [media[0]]: { - fontSize: '15px', - ['> div']: { - paddingTop: '6px', - gap: '6px', - fontSize: '13px', - }, - }, -}); diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx deleted file mode 100644 index 55316a63..00000000 --- a/src/components/SearchBar/SearchBar.tsx +++ /dev/null @@ -1,490 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { MARKET_CODES, SEARCH_CATEGORY_TEXT, STOCK_COUNTRY_TEXT } from '@ts/Constants'; -import { SEARCH_CATEGORY } from '@ts/Types'; -import { STORAGE_RECENT_ITEMS, getItemLocalStorage, setItemLocalStorage } from '@utils/LocalStorage'; -import { useIsMobile } from '@hooks/useIsMobile'; -import { webPath } from '@router/index'; -import { fetchAutoComplete, fetchSearchKeyword } from '@controllers/api'; -import { AutoCompleteItem, SearchBarResultItems } from '@controllers/api.Type'; -import { PopularKeywordQuery, useAutoComplete, usePopularStockFetchQuery } from '@controllers/query'; -import CancelSVG from '@assets/icons/cancel.svg?react'; -import DownSVG from '@assets/icons/down.svg?react'; -import SearchSVG from '@assets/icons/search.svg?react'; -import UpSVG from '@assets/icons/up.svg?react'; -import NoResultSVG from '@assets/noResult.svg?react'; -import { - SearchBarContainer, - SearchBarInput, - SearchBarLayout, - SearchBarResultContainer, - SearchBarResultContent, - SearchBarResultGridContainer, - SearchBarResultItemContainer, - SearchBarResultItemKeyword, - SearchBarResultItemSVG, - SearchBarResultItemSubtitle, - SearchBarResultItemTitle, - SearchBarResultLayout, - SearchBarResultLayoutContainer, - SearchBarResultSVG, - SearchBarResultSubtitle, - SearchBarResultTitle, - SearchBarSelectBox, - SearchBarSelectBoxItems, -} from './SearchBar.Style'; - -const matchCharacters = (query: string, text: string) => { - let queryIndex = 0; - - return [...text].map((char) => ({ - char, - isMatch: query[queryIndex] === char && !!++queryIndex, - })); -}; - -const useBlocker = (shouldBlock: boolean, onBlock: () => void, onAlwaysExecute?: () => void) => { - const navigate = useNavigate(); - const location = useLocation(); - const [previousLocation, setPreviousLocation] = useState(location); - - useEffect(() => { - const handleBeforeUnload = (event: PopStateEvent) => { - if (shouldBlock) { - event.preventDefault(); - navigate(previousLocation, { state: previousLocation.state }); - onBlock(); - } - if (onAlwaysExecute) onAlwaysExecute(); - }; - - window.onpopstate = handleBeforeUnload; - }, [shouldBlock, onBlock, onAlwaysExecute]); - - useEffect(() => { - setPreviousLocation(location); - }, [location]); -}; - -const useComponentFocus = ( - initialState: boolean, - ref: React.RefObject, -): [boolean, React.Dispatch>] => { - const [isFocus, setIsFocus] = useState(initialState); - - useEffect(() => ref.current?.[isFocus ? 'focus' : 'blur'](), [isFocus]); - - return [isFocus, setIsFocus]; -}; - -const useOutsideClick = (callback: () => void) => { - const ref = useRef(null); - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (ref.current && !ref.current.contains(event.target as Node)) { - callback(); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [callback]); - - return ref; -}; - -const SearchBarItemsComponent = ({ - type, - category, - resultItems, - handleItemClick, - onItemDelete = () => {}, - searchValue = '', - displayEmpty, -}: { - type: 'SEARCHED' | 'RECENT' | 'POPULAR'; - category: SEARCH_CATEGORY; - resultItems: SearchBarResultItems[]; - handleItemClick: (item: AutoCompleteItem) => void; - onItemDelete?: (item: AutoCompleteItem) => void; - searchValue?: string; - displayEmpty?: boolean; -}) => { - const width = `${type === 'RECENT' ? 50 : 100}%`; - const column = type === 'RECENT' ? 1 : type === 'POPULAR' ? 2 : category === 'STOCK' ? 3 : 2; - const title = - type === 'SEARCHED' ? '검색 결과' : `${type === 'RECENT' ? '최근' : '인기'} 검색 ${SEARCH_CATEGORY_TEXT[category]}`; - - const searchKeyword = resultItems[0]?.keyword; - const isHidden = type === 'RECENT' && !resultItems.length; - - if (isHidden) return; - return ( - - {type === 'POPULAR' || resultItems.length ? ( - <> - {title} - {type === 'SEARCHED' && category == 'KEYWORD' && ( - - {searchKeyword} 이(가) 가장 많이 업급된 종목순으로 노출됩니다. - - )} - - {resultItems.map((item, idx) => { - const { symbolName, symbol, exchangeNum, country, keywordNames, keyword, value } = item; - - return ( -
    - handleItemClick(item)}> - {type == 'POPULAR' && {idx + 1}} - {(category === 'STOCK' ? type === 'RECENT' || type === 'POPULAR' : type === 'SEARCHED') && ( - {STOCK_COUNTRY_TEXT[country]} 종목 - )} - - {type === 'SEARCHED' - ? category === 'STOCK' - ? matchCharacters(searchValue.toLocaleUpperCase(), symbolName).map(({ isMatch, char }, i) => - isMatch ? {char} : char, - ) - : symbolName - : value} - {type === 'SEARCHED' && category === 'STOCK' && ( -
    -
    - {matchCharacters(searchValue.toLocaleUpperCase(), symbol).map(({ isMatch, char }, i) => - isMatch ? ( - {char} - ) : ( - char - ), - )} -
    -
    {MARKET_CODES[exchangeNum]}
    -
    - )} -
    - {type === 'RECENT' && ( - - { - e.preventDefault(); - e.stopPropagation(); - onItemDelete(item); - }} - /> - - )} - {type === 'SEARCHED' && category == 'KEYWORD' && ( - <> - {searchKeyword} - - {keywordNames.filter((e) => e !== keyword)[0]} - - - )} -
    -
    - ); - })} -
    - - ) : ( - displayEmpty && ( - - - - ) - )} -
    - ); -}; - -const SearchBar = () => { - const navigate = useNavigate(); - const isMobile = useIsMobile(); - - const SEARCHED_RESULT_MAX_LENGTH = { - STOCK: 15, - KEYWORD: isMobile ? 15 : 10, - }; - - const [inputValue, setInputValue] = useState(''); - const searchValue = inputValue.replace(/\s+/g, '').toUpperCase(); - - const inputRef = useRef(null); - const selectBoxRef = useRef(null); - - const [isActiveSearchBar, setIsActiveSearchBar] = useState(false); - const [isFocusSelectBox, setIsFocusSelectBox] = useComponentFocus(false, selectBoxRef); - const [isFocusInput, setIsFocusInput] = useComponentFocus(false, inputRef); - - const [selectedCategory, setSelectedCategory] = useState('STOCK'); - const [focusedItem, setFocusedItem] = useState({ - idx: -1, - type: '', - }); - focusedItem; - - const resultContainerRef = useRef(null); - const resultContainerHeight = window.innerHeight - (resultContainerRef.current?.getBoundingClientRect().top ?? 0); - - const [recentStocks, setRecentStocks] = useState( - getItemLocalStorage(STORAGE_RECENT_ITEMS['STOCK'], []), - ); - const [recentKeyowrds, setRecentKeyowrds] = useState( - getItemLocalStorage(STORAGE_RECENT_ITEMS['KEYWORD'], []), - ); - const [recentItems, setRecentItems] = - selectedCategory == 'STOCK' ? [recentStocks, setRecentStocks] : [recentKeyowrds, setRecentKeyowrds]; - - const [popularStocks] = usePopularStockFetchQuery(); - const [popularKeywords] = PopularKeywordQuery(); - const popularItems = selectedCategory == 'STOCK' ? popularStocks : popularKeywords; - - const [searchedStocks, setSearchedStocks] = useAutoComplete(fetchAutoComplete, 'symbolName'); - const [searchedKeywords, setSearchedKeywords] = useAutoComplete(fetchSearchKeyword, 'keyword'); - const searchedItems = selectedCategory == 'STOCK' ? searchedStocks : searchedKeywords; - - useEffect(() => { - const setSearchedItems = selectedCategory == 'STOCK' ? setSearchedStocks : setSearchedKeywords; - setSearchedItems(searchValue); - }, [searchValue, selectedCategory]); - - useBlocker( - isMobile && isActiveSearchBar, - () => updateActiveSearchBar(false), - () => inputRef.current?.blur(), - ); - - // const searchBarInputKeyDown = async (e: React.KeyboardEvent) => { - // // const length = searchValue == '' ? (selectedCategory == 'STOCK' ? ()):(2); - - // if (searchValue == '') { - // const recentItems = selectedCategory == 'STOCK' ? recentStocks : recentKeyowrds; - // const popularItems = - // selectedCategory == 'STOCK' ? popularStocks : popularKeywords[selectedCountry]; - // } else { - // const searchedItems = selectedCategory == 'STOCK' ? searchedStocks : searchedKeywords; - // } - - // // if (e.key == 'ArrowDown') { - // // e.preventDefault(); - // // setFocusIdx((prev) => - // // stockName == '' ? (prev + 1) % stockSearchInfo.length : (prev + 1) % searchedResult.length, - // // ); - // // } else if (e.key == 'ArrowUp') { - // // e.preventDefault(); - // // setFocusIdx((prev) => - // // stockName == '' - // // ? ((prev == -1 ? 0 : prev) + stockSearchInfo.length - 1) % stockSearchInfo.length - // // : ((prev == -1 ? 0 : prev) + searchedResult.length - 1) % searchedResult.length, - // // ); - // // } - - // // else if (e.key == 'Escape') { - // // (document.activeElement as HTMLElement).blur(); - // // setFocusIdx(-1); - // // } else if (e.key === 'Enter') { - // // if (focusIdx === -1) return; - // // const name = - // // stockName == '' - // // ? stockSearchInfo[focusIdx].symbolName - // // : searchedResult[focusIdx].symbolName; - // // const country = - // // stockName == '' - // // ? stockSearchInfo[focusIdx].country - // // : searchedResult[focusIdx].country; - // // const result = await fetchSearchSymbolName(name, country); - // // if (result) { - // // const curStockSearchInfo: StockSearchInfo = { - // // symbolName: result.symbolName, - // // country: result.country, - // // }; - // // handleSearch(curStockSearchInfo); - // // } - // // } - // }; - - const handleSearchValueChange = (e: React.ChangeEvent) => { - const value = e.target.value; - - setInputValue(value); - setFocusedItem((prev: any) => ({ ...prev, idx: -1 })); - }; - - const updateActiveSearchBar = (active: boolean) => { - const onScrollEvent = () => { - function lockScroll() { - window.scrollTo(0, 0); - } - window.requestAnimationFrame(lockScroll); - }; - - if (isMobile) { - document.documentElement.style.overflow = active ? 'hidden' : ''; - document.body.style.overflow = active ? 'hidden' : ''; - document.body.style.position = active ? 'fixed' : ''; // iOS Safari - document.body.style.inset = active ? '0px' : ''; - document.body.style.left = active ? '0px' : ''; - document.body.style.right = active ? '0px' : ''; - window.onscroll = active ? onScrollEvent : null; - - if (active) { - window.scrollTo({ top: 0, behavior: 'smooth' }); - } else { - resultContainerRef.current?.scrollTo({ top: 0, behavior: 'smooth' }); - } - } - setIsActiveSearchBar(active); - }; - - // LocalStorage - - const addRecentItem = (item: AutoCompleteItem) => { - const { value, country } = item; - const updatedItems = [item, ...recentItems.filter((e) => e.value !== value || e.country !== country)]; - - setItemLocalStorage(STORAGE_RECENT_ITEMS[selectedCategory], updatedItems); - setRecentItems(updatedItems); - }; - - const deleteRecentItem = (item: AutoCompleteItem) => { - const { value, country } = item; - const updatedItems = recentItems.filter((e) => e.value !== value || e.country !== country); - - setItemLocalStorage(STORAGE_RECENT_ITEMS[selectedCategory], updatedItems); - setRecentItems(updatedItems); - }; - - const handleSearch = (item: AutoCompleteItem) => { - const { symbolName, country } = item; - addRecentItem(item); - navigate(webPath.search(), { state: { symbolName, country } }); - setInputValue(''); - (document.activeElement as HTMLElement).blur(); - updateActiveSearchBar(false); - }; - - // SearchBar EventHandler - - const handleSearchBarInputFocus = () => { - setIsFocusInput(true); - updateActiveSearchBar(true); - }; - - // SelectBox EventHandler - - const handleSelectBoxClick = () => { - if (!isActiveSearchBar && !isFocusSelectBox) updateActiveSearchBar(true); - setIsFocusSelectBox(!isFocusSelectBox); - }; - - const handleSelectBoxItemClick = (category: SEARCH_CATEGORY) => { - setSelectedCategory(category); - setIsFocusSelectBox(false); - setInputValue(''); - }; - - const handleItemClick = (item: AutoCompleteItem) => { - if (searchValue.length || selectedCategory == 'STOCK') handleSearch(item); - else setInputValue(item.value); - }; - - // Component - - const searchBarContainerRef = useOutsideClick(() => updateActiveSearchBar(false)); - - return ( - <> - - - setIsFocusSelectBox(false)} - tabIndex={-1} - minimize={isFocusInput || (!!inputValue.length && !isFocusSelectBox)} - > - - e === selectedCategory)} - > - {(Object.keys(SEARCH_CATEGORY_TEXT) as SEARCH_CATEGORY[]).map((category) => ( -
  • handleSelectBoxItemClick(category)}> - {SEARCH_CATEGORY_TEXT[category]} -
  • - ))} -
    -
    - - - setIsFocusInput(false)} - placeholder="검색어를 입력하세요." - /> - {!isActiveSearchBar ? ( - - ) : ( - { - updateActiveSearchBar(false); - setInputValue(''); - }} - /> - )} - - - - - - {searchValue == '' ? ( - <> - - - - ) : ( - - )} - - - -
    -
    - - ); -}; - -export default SearchBar; diff --git a/src/components/SlideView/SlideView.Style.ts b/src/components/SlideView/SlideView.Style.ts index a0bf2eeb..3b2fb7a2 100644 --- a/src/components/SlideView/SlideView.Style.ts +++ b/src/components/SlideView/SlideView.Style.ts @@ -1,17 +1,17 @@ import styled from '@emotion/styled'; import { media, theme } from '@styles/themes'; -export const SlideContainer = styled.div({ +const SlideContainer = styled.div({ display: 'flex', flexDirection: 'column', }); -export const SlideItemContainer = styled.div({ +const SlideItemContainer = styled.div({ overflowX: 'scroll', overflowY: 'hidden', boxSizing: 'border-box', width: '100%', - padding: '0px 24px', + // padding: '0px 24px', msOverflowStyle: 'none', scrollBehavior: 'auto', @@ -23,11 +23,11 @@ export const SlideItemContainer = styled.div({ }, [media[0]]: { - padding: '0px 16px', + // padding: '0px 16px', }, }); -export const SlideItemContents = styled.div( +const SlideItemContents = styled.div( { position: 'relative', @@ -49,7 +49,7 @@ export const SlideItemContents = styled.div( }), ); -export const SlideItem = styled.div( +const SlideItem = styled.div( { position: 'absolute', top: 0, @@ -73,7 +73,7 @@ export const SlideItem = styled.div( }), ); -export const SlideArrowContainer = styled.div( +const SlideArrowContainer = styled.div( { position: 'relative', @@ -85,7 +85,7 @@ export const SlideArrowContainer = styled.div( }), ); -export const SlideArrowContents = styled.div( +const SlideArrowContents = styled.div( { position: 'absolute', top: '18px', @@ -121,3 +121,5 @@ export const SlideArrowContents = styled.div( }, }), ); + +export { SlideContainer, SlideItemContainer, SlideItemContents, SlideItem, SlideArrowContainer, SlideArrowContents }; diff --git a/src/components/SlideView/SlideView.tsx b/src/components/SlideView/SlideView.tsx index bfe905b3..a62d5662 100644 --- a/src/components/SlideView/SlideView.tsx +++ b/src/components/SlideView/SlideView.tsx @@ -27,6 +27,7 @@ const SlideView = ({ keyName, list, count }: { keyName: string; list: JSX.Elemen const itemConatinerRef = useRef(null); const [selected, setSelected] = useState(0); const [height, setHeight] = useState(0); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_, setWidth, widthRef] = useStateRef(0); const isMobile = useIsMobile(); diff --git a/src/components/StockSlotMachine/StockSlotMachine.tsx b/src/components/StockSlotMachine/StockSlotMachine.tsx deleted file mode 100644 index 130b4c05..00000000 --- a/src/components/StockSlotMachine/StockSlotMachine.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { AnimatePresence, Variants } from 'framer-motion'; -import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { scoreToIndex } from '@utils/ScoreConvert'; -import { webPath } from '@router/index'; -import { ARRAY_STOCK_SCORE_IMAGE, ARRAY_STOCK_SCORE_TITLE } from '@constants/stockScore'; -import { ImgDiv } from '@components/Common/Common'; -import DownSVG from '@assets/icons/down.svg?react'; -import UpSVG from '@assets/icons/up.svg?react'; -import { - ScoreSlotMachineContainer, - ScoreSlotMachineContent, - ScoreSlotMachineTitle, - SlotMachineItemContainer, - SlotMachineItemMotionDiv, - StockCardItemDeltaScore, - StockCardItemScore, -} from './stockSlotMachine.Style'; - -const getDuration = (animationTime: number, idx: number, lastIndex: number) => { - const a = (3 * animationTime) / (lastIndex * lastIndex * lastIndex); - const b = a * idx * idx; - return b; -}; - -const variants: Variants = { - initial: (isLast: boolean) => ({ - scaleY: 1, - y: '-100%', - opacity: 1, - filter: !isLast ? 'blur(2px)' : 'blur(0px)', - }), - animate: (isLast: boolean) => ({ - scaleY: 1, - y: '0%', - opacity: 1, - filter: !isLast ? 'blur(2px)' : 'blur(0px)', - }), - exit: (isLast: boolean) => ({ - scaleY: 1, - y: '100%', - opacity: 1, - filter: !isLast ? 'blur(2px)' : 'blur(0px)', - }), -}; - -const ScoreSlotMachineItemCard = ({ - slotMachineType, - idx, - diff = 0, -}: { - slotMachineType: 'TITLE' | 'IMAGE' | 'SCORE'; - idx: number; - diff?: number; -}) => { - const deltaSVG = !diff ? ' -' : diff > 0 ? : ; - - return slotMachineType == 'TITLE' ? ( - ARRAY_STOCK_SCORE_TITLE[idx % ARRAY_STOCK_SCORE_TITLE.length] - ) : slotMachineType == 'IMAGE' ? ( - - ) : ( - - {idx}점 - - {Math.abs(diff)} - {deltaSVG} - - - ); -}; - -const ScoreSlotMachineItem = ({ - stockName, - stockScore, - stockDiff, - slotMachineType, -}: { - stockName?: string; - stockScore: number; - stockDiff?: number; - slotMachineType: 'TITLE' | 'IMAGE' | 'SCORE'; -}) => { - const [currentIndex, setCurrentIndex] = useState(0); - const scoreIndex = scoreToIndex(stockScore); - const lastIndex = slotMachineType == 'TITLE' ? 10 : slotMachineType == 'IMAGE' ? 15 : 20; - const animationTime = slotMachineType == 'TITLE' ? 0.5 : slotMachineType == 'IMAGE' ? 1 : 1.5; - - useEffect(() => { - setCurrentIndex(0); - }, [stockName]); - - useEffect(() => { - if (currentIndex == lastIndex) return; - const interval = setInterval( - () => { - setCurrentIndex((prev) => { - return prev < lastIndex ? prev + 1 : prev; - }); - }, - getDuration(animationTime, currentIndex, lastIndex) * 1000, - ); - - return () => clearInterval(interval); - }, [currentIndex, lastIndex]); - - return ( - - - {Array.from({ length: lastIndex + 1 }, (_, i) => { - return ( - i == currentIndex && ( - = lastIndex * 0.95} - variants={variants} - initial="initial" - animate="animate" - exit="exit" - transition={{ - duration: getDuration(animationTime, currentIndex, lastIndex), - ease: currentIndex == lastIndex ? 'easeOut' : 'linear', - }} - > - - - ) - ); - })} - - - ); -}; - -const ScoreSlotMachine = ({ - stockName, - active, - stockScore, - tabIndex, - stockDiff, - country, -}: { - stockName?: string; - active?: boolean; - stockScore: number; - tabIndex?: number; - stockDiff?: number; - country: string; -}) => { - const navigate = useNavigate(); - - const handleClick = () => { - navigate(webPath.search(), { state: { symbolName: stockName, country: country } }); - }; - - return ( - {}}> - {active && {stockName}} - - - - - - - ); -}; - -export default ScoreSlotMachine; diff --git a/src/components/StockSlotMachine/stockSlotMachine.Style.ts b/src/components/StockSlotMachine/stockSlotMachine.Style.ts deleted file mode 100644 index 0d3eaa49..00000000 --- a/src/components/StockSlotMachine/stockSlotMachine.Style.ts +++ /dev/null @@ -1,113 +0,0 @@ -import styled from '@emotion/styled'; -import { motion } from 'framer-motion'; -import { deltaScoreToColor } from '@utils/ScoreConvert'; -import { media, theme } from '@styles/themes'; - -const ScoreSlotMachineContainer = styled.div( - { - display: 'flex', - flexDirection: 'column', - fontWeight: '700', - lineHeight: '1', - gap: '24px', - - [media[0]]: { - gap: '13px', - marginBottom: '0.5em', - }, - [':hover > div > div']: { - backgroundColor: theme.colors.grayscale90, - }, - }, - ({ active }: { active?: boolean }) => active && { cursor: 'pointer' }, -); - -const ScoreSlotMachineTitle = styled.span({ - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - overflow: 'hidden', - width: '100%', - fontSize: '24px', - [media[0]]: { - fontSize: '17px', - }, -}); - -const ScoreSlotMachineContent = styled.div({ - display: 'flex', - gap: '12px', - [media[0]]: { - gap: '12px', - }, -}); - -const SlotMachineItemContainer = styled.div({ - backgroundColor: theme.colors.grayscale100, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - borderRadius: '12px', - width: '100%', - overflow: 'hidden', - position: 'relative', - height: '320px', - - [media[0]]: { - height: '100px', - borderRadius: '8px', - }, -}); - -const SlotMachineItemMotionDiv = styled(motion.div)({ - fontSize: '72px', - color: theme.colors.primary0, - fontWeight: '700', - position: 'relative', - width: '100%', - height: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - ['img']: { - height: '100%', - width: '100%', - objectFit: 'cover', - }, - [media[0]]: { - fontSize: '24px', - }, -}); - -const StockCardItemScore = styled.div({ - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - color: theme.colors.primary0, - - fontSize: '1.0em', - gap: '0.25em', -}); -const StockCardItemDeltaScore = styled.div(({ delta }: { delta: number }) => ({ - display: 'flex', - alignItems: 'center', - - fontSize: '0.8em', - gap: '4px', - color: deltaScoreToColor(delta), - padding: '0 0.4em', - - ['svg']: { - height: '0.5em', - width: 'auto', - fill: deltaScoreToColor(delta), - }, -})); -export { - ScoreSlotMachineContainer, - SlotMachineItemContainer, - ScoreSlotMachineContent, - SlotMachineItemMotionDiv, - ScoreSlotMachineTitle, - StockCardItemScore, - StockCardItemDeltaScore, -}; diff --git a/src/components/Text/Text.ts b/src/components/Text/Text.ts index da701a01..49d77c95 100644 --- a/src/components/Text/Text.ts +++ b/src/components/Text/Text.ts @@ -1,6 +1,6 @@ import styled from '@emotion/styled'; -import { TextBodyProps, TextDetailProps, TextDisplayProps, TextHeadingProps, TextTitleProps } from './Text.Props'; import { media, theme } from '../../styles/themes'; +import { TextBodyProps, TextDetailProps, TextDisplayProps, TextHeadingProps, TextTitleProps } from './Text.Props'; /** * TextHeading diff --git a/src/components/Toast/Toast.Style.ts b/src/components/Toast/Toast.Style.ts new file mode 100644 index 00000000..f5f6f87d --- /dev/null +++ b/src/components/Toast/Toast.Style.ts @@ -0,0 +1,59 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const ToastContent = styled.div({ + background: `${theme.colors.sub_blue6}CC`, + display: 'flex', + alignItems: 'center', + borderRadius: '5px', + border: '1px solid rgba(73, 80, 87, 0.5)', + boxSizing: 'border-box', + backdropFilter: 'blur(5px)', + boxShadow: '0px 4px 20px 0px rgba(0, 0, 0, 0.5)', + gap: '10px', + width: '100%', + padding: '12px 16px', + + ['>svg']: { + width: '20px', + height: 'auto', + aspectRatio: '1 / 1', + fill: theme.colors.sub_white, + }, + + ['>p']: { + margin: '0', + ...theme.font.detail12Semibold, + color: theme.colors.sub_gray2, + + ['&.cancel']: { + color: theme.colors.sub_gray5, + textDecoration: 'underline', + marginLeft: 'auto', + cursor: 'pointer', + }, + }, +}); + +const ToastContainer = styled.div( + ({ closing }: { closing: boolean }) => ({ + opacity: closing ? 0 : 1, + }), + { + transition: 'opacity 0.3s ease-in-out', + position: 'fixed', + bottom: '0px', + left: '50%', + transform: 'translateX(-50%)', + maxWidth: '1280px', + background: 'linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #101010 81.02%)', + width: '100%', + zIndex: '1000', + padding: '96px 20px', + display: 'flex', + justifyContent: 'center', + boxSizing: 'border-box', + }, +); + +export { ToastContent, ToastContainer }; diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx new file mode 100644 index 00000000..f5642aa2 --- /dev/null +++ b/src/components/Toast/Toast.tsx @@ -0,0 +1,16 @@ +import { ToastState } from '@hooks/useToast'; +import { ToastContainer, ToastContent } from './Toast.Style'; + +const Toast = ({ toast, hideToast }: { toast: ToastState; hideToast: () => void }) => { + const { message, enabled, closing } = toast; + + if (!enabled) return null; + + return ( + + {message} + + ); +}; + +export default Toast; diff --git a/src/config/oauth.ts b/src/config/oauth.ts new file mode 100644 index 00000000..b9aa8522 --- /dev/null +++ b/src/config/oauth.ts @@ -0,0 +1,79 @@ +export const URL_SCHEME = 'humanzipyoapp://'; + +export const SOCIAL_PROVIDER = { + GOOGLE: 'google', + KAKAO: 'kakao', + NAVER: 'naver', + APPLE: 'apple', +} as const; + +export type SocialProvider = (typeof SOCIAL_PROVIDER)[keyof typeof SOCIAL_PROVIDER]; + +export interface AuthConfig { + endpoint: string; + clientId: string; + redirectUri: string; + responseType: string; + scope?: string; + accessType?: string; + prompt?: string; + includeGrantedScopes?: string; + responseMode?: string; +} + +// redirectUri를 동적으로 생성하는 함수 +const getRedirectUri = (provider: string): string => { + if (typeof window === 'undefined') { + return ''; // SSR 환경에서는 빈 문자열 반환 + } + return `${window.location.origin}/login/oauth2/code/${provider}`; +}; + +// 런타임에 AUTH_CONFIGS를 생성하는 함수 +const createAuthConfigs = (): Record => ({ + [SOCIAL_PROVIDER.GOOGLE]: { + endpoint: 'https://accounts.google.com/o/oauth2/v2/auth', + clientId: import.meta.env.VITE_GOOGLE_CLIENT_ID, + responseType: 'code', + scope: 'openid email profile', + accessType: 'offline', + prompt: 'consent', + includeGrantedScopes: 'true', + redirectUri: getRedirectUri('google'), + }, + [SOCIAL_PROVIDER.KAKAO]: { + endpoint: 'https://kauth.kakao.com/oauth/authorize', + clientId: import.meta.env.VITE_KAKAO_API_KEY, + responseType: 'code', + redirectUri: getRedirectUri('kakao'), + }, + [SOCIAL_PROVIDER.NAVER]: { + endpoint: 'https://nid.naver.com/oauth2.0/authorize', + clientId: import.meta.env.VITE_NAVER_CLIENT_ID, + responseType: 'code', + redirectUri: getRedirectUri('naver'), + }, + [SOCIAL_PROVIDER.APPLE]: { + endpoint: 'https://appleid.apple.com/auth/authorize', + clientId: import.meta.env.VITE_APPLE_CLIENT_ID, + responseType: 'code', + responseMode: 'query', + scope: '', + redirectUri: getRedirectUri('apple'), + }, +}); + +let _authConfigs: Record | null = null; + +// AUTH_CONFIGS를 lazy하게 초기화 +export const AUTH_CONFIGS = new Proxy({} as Record, { + get: (_, provider: string | symbol) => { + if (typeof provider === 'symbol') return undefined; + + if (!_authConfigs) { + _authConfigs = createAuthConfigs(); + } + + return _authConfigs[provider as SocialProvider]; + }, +}); diff --git a/src/config/webview.ts b/src/config/webview.ts new file mode 100644 index 00000000..0f9f6801 --- /dev/null +++ b/src/config/webview.ts @@ -0,0 +1,11 @@ +export const MESSAGE_TYPES = { + OPEN_EXTERNAL_BROWSER: 'OPEN_EXTERNAL_BROWSER', + OAUTH_LOGIN_EXTERNAL: 'OAUTH_LOGIN_EXTERNAL', + AUTH_SUCCESS: 'AUTH_SUCCESS', + AUTH_ERROR: 'AUTH_ERROR', + TOKEN: 'TOKEN', + NEED_REGISTER: 'NEED_REGISTER', + REQUEST_NOTIFICATION_PERMISSION: 'REQUEST_NOTIFICATION_PERMISSION', +} as const; + +export type MessageType = (typeof MESSAGE_TYPES)[keyof typeof MESSAGE_TYPES]; diff --git a/src/constants/patternTypes.ts b/src/constants/patternTypes.ts new file mode 100644 index 00000000..e44b711f --- /dev/null +++ b/src/constants/patternTypes.ts @@ -0,0 +1,29 @@ +import { theme } from '@styles/themes'; + +export type HumanType = (typeof HUMAN_TYPE_LIST)[number]; +export const HUMAN_TYPE_LIST = [ + { + type: '가치 선점형', + emoji: '💎', + description: '인간지표 낮을 때 매수 → 수익', + background: theme.colors.sub_blue5, + }, + { + type: '트렌드 선점형', + emoji: '✅', + description: '인간지표 높을 때 매수 → 수익', + background: theme.colors.sub_blue6, + }, + { + type: '역행 투자형', + emoji: '📉', + description: '점수 낮을 때 매수 → 손실', + background: theme.colors.sub_blue8, + }, + { + type: '후행 추종형', + emoji: '❗', + description: '인간지표 높을 때 매수 → 손실', + background: theme.colors.sub_blue9, + }, +] as const; diff --git a/src/constants/stockScore.ts b/src/constants/stockScore.ts index 634669ec..7efcda9e 100644 --- a/src/constants/stockScore.ts +++ b/src/constants/stockScore.ts @@ -1,17 +1,19 @@ +import badPNG from '../assets/stockScore/bad.png'; import excellentPNG from '../assets/stockScore/excellent.png'; import goodPNG from '../assets/stockScore/good.png'; import normalPNG from '../assets/stockScore/normal.png'; import poorPNG from '../assets/stockScore/poor.png'; -import badPNG from '../assets/stockScore/bad.png'; -const stockScoreTitle = { - perfect: '"s"', - excellent: '"대호황"', - good: '"호황"', - normal: '"어?"', - poor: '"곰탕"', - bad: '"대곰탕"', - fuck: '""', +export type StockScoreTitle = 'perfect' | 'excellent' | 'good' | 'normal' | 'poor' | 'bad' | 'fuck'; + +const stockScoreTitle: Record = { + perfect: 's', + excellent: '대호황', + good: '호황', + normal: '어?', + poor: '곰탕', + bad: '대곰탕', + fuck: '', }; const stockScoreImage = { @@ -24,7 +26,19 @@ const stockScoreImage = { fuck: undefined, }; -const ARRAY_STOCK_SCORE_TITLE = [stockScoreTitle.bad, stockScoreTitle.poor, stockScoreTitle.normal, stockScoreTitle.good, stockScoreTitle.excellent]; -const ARRAY_STOCK_SCORE_IMAGE = [stockScoreImage.bad, stockScoreImage.poor, stockScoreImage.normal, stockScoreImage.good, stockScoreImage.excellent]; +const ARRAY_STOCK_SCORE_TITLE = [ + stockScoreTitle.bad, + stockScoreTitle.poor, + stockScoreTitle.normal, + stockScoreTitle.good, + stockScoreTitle.excellent, +]; +const ARRAY_STOCK_SCORE_IMAGE = [ + stockScoreImage.bad, + stockScoreImage.poor, + stockScoreImage.normal, + stockScoreImage.good, + stockScoreImage.excellent, +]; export { stockScoreTitle, stockScoreImage, ARRAY_STOCK_SCORE_TITLE, ARRAY_STOCK_SCORE_IMAGE }; diff --git a/src/controllers/api.ts b/src/controllers/api.ts deleted file mode 100644 index ee6db5e9..00000000 --- a/src/controllers/api.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { WordFrequency } from '@ts/Interfaces'; -import { STOCK_COUNTRY } from '@ts/Types'; -import { PERIOD_CODE, PopularStocks } from './api.Type'; -import { - fetchChartMock, - fetchIndexScoreMock, - fetchKeywordsMock, - fetchPopularStocksMock, - fetchRelevantMock, - fetchScoreCardMock, - fetchScoreMock, - fetchSearchSymbolNameMock, - fetchSearchWordCloudMock, -} from './mock'; - -const baseURL = import.meta.env.VITE_BASE_URL; - -const Headers = { 'content-type': 'application/json' }; - -const wait = (timeToDelay: number) => new Promise((resolve) => setTimeout(resolve, timeToDelay)); - -const enableMock = false; - -const fetchData = async (path: string) => { - try { - const url = `${baseURL}${path}`; - const res = await fetch(url, { method: 'GET', headers: Headers }); - if (!res.ok) { - throw new Error(`${res.status} Error!!`); - } - await wait(0); - return await res.json(); - } catch (error) { - throw error; - } -}; - -const fetchScore = async (id: number, country: string) => { - if (enableMock) return fetchScoreMock; - return fetchData(`/${id}/score/${country}`); -}; - -const fetchRelevant = async (id: number) => { - if (enableMock) return fetchRelevantMock; - return fetchData(`/stock/${id}/relevant`); -}; - -const fetchHotStocks = async (country: string) => { - // return fetchScoreCardMock; - return fetchData(`/stock/hot/${country}`); -}; - -const fetchRisingStocks = async (country: string) => { - if (enableMock) return fetchScoreCardMock; - return fetchData(`/stock/rising/${country}`); -}; - -const fetchDescentStocks = async (country: string) => { - if (enableMock) return fetchScoreCardMock; - return fetchData(`/stock/descent/${country}`); -}; - -const fetchSearchSymbolName = (symbolname: string, country: STOCK_COUNTRY) => { - if (enableMock) return fetchSearchSymbolNameMock; - return fetchData(`/stock/search/${symbolname}/${country}`); -}; - -const fetchRealStockInfo = (stockId: number, country: string) => { - return fetchData(`/stock/${stockId}/info/${country}`); -}; - -const fetchKeywords = (country: string): Promise => { - if (enableMock) return Promise.resolve(fetchKeywordsMock); - return fetchData(`/keyword/popular/${country}`); -}; - -const fetchStockTable = (category: string, country: string) => { - return fetchData(`/stock/category/${category}/${country}`); -}; - -const fetchIndexScore = () => { - if (enableMock) return fetchIndexScoreMock; - return fetchData(`/score/index`); -}; - -// SearchTitle - -const fetchStockSummary = (symbol: string, country: STOCK_COUNTRY) => { - return fetchData(`/stock/summary/${symbol}/${country}`); -}; - -// WordCloud - -const fetchSearchWordCloud = (symbol: string, country: string): Promise => { - if (enableMock) return Promise.resolve(fetchSearchWordCloudMock); - return fetchData(`/wordcloud/${symbol}/${country}`); -}; - -// Chart - -const fetchStockChart = async (id: number, periodCode: PERIOD_CODE, startDate: string, endDate: string) => { - if (enableMock) return fetchChartMock; - return fetchData(`/stock/${id}/chart/{country}?periodCode=${periodCode}&startDate=${startDate}&endDate=${endDate}`); -}; - -// SearchBar - -const fetchAutoComplete = (name: string) => { - return fetchData(`/stock/autocomplete?keyword=${name}`); -}; - -const fetchSearchKeyword = (keywordName: string) => { - return fetchData(`/keyword/${keywordName}/stocks`); -}; - -const fetchPopularStocks = (): Promise => { - if (enableMock) return Promise.resolve(fetchPopularStocksMock as PopularStocks[]); - return fetchData(`/stock/rankings/hot`); -}; - -const fetchPopularKeywords = (): Promise => { - return fetchData('/keyword/rankings'); -}; - -export { - fetchScore, - fetchRelevant, - fetchStockChart, - fetchHotStocks, - fetchRisingStocks, - fetchDescentStocks, - fetchAutoComplete, - fetchSearchSymbolName, - fetchSearchWordCloud, - fetchRealStockInfo, - fetchKeywords, - fetchStockTable, - fetchIndexScore, - fetchSearchKeyword, - fetchPopularStocks, - fetchPopularKeywords, - fetchStockSummary, -}; diff --git a/src/controllers/auth/api.ts b/src/controllers/auth/api.ts new file mode 100644 index 00000000..3b51856c --- /dev/null +++ b/src/controllers/auth/api.ts @@ -0,0 +1,130 @@ +import { Headers, baseURL, fetchAuthData, fetchData, wait } from '@controllers/common/base'; + +export type ProviderKey = 'kakao' | 'google' | 'naver' | 'apple'; + +// GET /auth/nickname +export const fetchAuthNickname = async (nickname: string) => { + const url = `${baseURL}/auth/nickname?nickname=${nickname}`; + const res = await fetch(url, { + method: 'GET', + headers: { + ...Headers, + }, + }); + if (!res.ok) { + throw new Error(`${res.status} Error!!`); + } + await wait(0); + return await res.json(); +}; + +export const fetchOAuth2Login = async (_code: string, _state: string, provider: ProviderKey) => { + const allowedProviders: ProviderKey[] = ['kakao', 'google', 'naver', 'apple']; + if (!allowedProviders.includes(provider)) { + throw new Error(`Unknown OAuth provider: ${provider}`); + } + + const code = encodeURIComponent(_code); + const state = encodeURIComponent(_state); + + const url = `/auth/login/${provider}?code=${code}&state=${state}`; + + return fetchData(url); +}; + +export const fetchAuthRegister = async ( + imageBase64: string, + email: string, + nickname: string, + birth_date: string, + marketingAgreement: boolean, + provider: string, +) => { + const formData = new FormData(); + formData.append('email', email); + formData.append('nickname', nickname); + formData.append('birth_date', birth_date); + formData.append('marketingAgreement', String(marketingAgreement)); + formData.append('provider', provider); + + if (imageBase64) { + const byteString = atob(imageBase64.split(',')[1]); + const mimeString = imageBase64.split(',')[0].split(':')[1].split(';')[0]; + + const ab = new ArrayBuffer(byteString.length); + const ia = new Uint8Array(ab); + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + + const blob = new Blob([ab], { type: mimeString }); + const file = new File([blob], 'profile.png', { type: mimeString }); + formData.append('image', file); + } + + return fetchData( + '/auth/register', + { + method: 'POST', + body: formData, + }, + true, + ); +}; + +// PATCH /user/image +export const fetchUpdateUserImage = async (imageBase64: string) => { + const formData = new FormData(); + if (imageBase64) { + const byteString = atob(imageBase64.split(',')[1]); + const mimeString = imageBase64.split(',')[0].split(':')[1].split(';')[0]; + + const ab = new ArrayBuffer(byteString.length); + const ia = new Uint8Array(ab); + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + + const blob = new Blob([ab], { type: mimeString }); + const file = new File([blob], 'profile.png', { type: mimeString }); + formData.append('image', file); + } + + return fetchAuthData( + '/user/image', + { + method: 'PATCH', + body: formData, + }, + true, + ); +}; + +// PATCH /user/profile +export const fetchUpdateUserProfile = async (nickname: string, birth_date: string) => { + return fetchAuthData('/user/profile', { + method: 'PATCH', + body: JSON.stringify({ + nickname: nickname, + birth_date: birth_date, + marketing_agreement: true, + }), + }); +}; + +// POST /auth/logout +export const fetchAuthLogout = async () => { + return fetchAuthData('/auth/logout', { + method: 'POST', + body: JSON.stringify({ + refreshToken: localStorage.getItem('refresh_token'), + }), + }); +}; + +// DELETE /auth/withdraw +export const fetchAuthWithdraw = async () => { + return fetchAuthData('/auth/withdraw', { + method: 'DELETE', + }); +}; diff --git a/src/controllers/common/base.ts b/src/controllers/common/base.ts new file mode 100644 index 00000000..5c286c31 --- /dev/null +++ b/src/controllers/common/base.ts @@ -0,0 +1,113 @@ +import { webPath } from '@router/index'; + +const baseURL = import.meta.env.VITE_BASE_URL; +const Headers = { 'Content-Type': 'application/json' } as const; + +const wait = (timeToDelay: number) => new Promise((resolve) => setTimeout(resolve, timeToDelay)); + +const enableMock = false; + +const fetchData = async (path: string, init: RequestInit = {}, isFormData: boolean = false) => { + const url = `${baseURL}${path}`; + const res = await fetch(url, { + method: 'GET', + ...init, + headers: { + ...(isFormData ? {} : Headers), + ...init.headers, + }, + }); + + const data = await res.json(); + if (data.state) return data; + if (!res.ok) { + throw new Error(`${res.status} Error!!`); + } + await wait(0); + return data; +}; + +const fetchAuthData = async (path: string, init: RequestInit = {}, isFormData: boolean = false) => { + const url = `${baseURL}${path}`; + const token = localStorage.getItem('access_token'); + let res = await fetch(url, { + method: 'GET', + ...init, + headers: { + ...(isFormData ? {} : Headers), + ...(init.headers as any), + Authorization: `Bearer ${token}`, + }, + credentials: 'include', // 쿠키 포함 + }); + + if (res.status === 401) { + const refreshToken = localStorage.getItem('refresh_token'); + + const reissueRes = await fetch(`${baseURL}/auth/reissue`, { + method: 'POST', + headers: { + ...Headers, + }, + body: JSON.stringify({ + refreshToken: refreshToken, + }), + }); + + if (!reissueRes.ok) { + alert('토큰 재발급 실패. 재로그인 필요'); + + window.location.href = webPath.login; + + throw new Error('토큰 재발급 실패. 재로그인 필요'); + } + + const { access_token, refresh_token: newRefreshToken } = await reissueRes.json(); + + // 새 토큰 저장 + localStorage.setItem('access_token', access_token); + localStorage.setItem('refresh_token', newRefreshToken); + + // localStorage 변경을 React state에 반영하기 위한 커스텀 이벤트 발생 + window.dispatchEvent(new CustomEvent('localStorageChange', { detail: { key: 'access_token' } })); + window.dispatchEvent(new CustomEvent('localStorageChange', { detail: { key: 'refresh_token' } })); + + (window as any).ReactNativeWebView?.postMessage(JSON.stringify({ type: 'TOKEN', token: access_token })); + // const token = localStorage.getItem('access_token'); + // if (token && window.ReactNativeWebView) { + // window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'TOKEN', token })); + // } + // 원래 요청 재시도 + res = await fetch(url, { + method: 'GET', + ...init, + headers: { + ...Headers, + ...(init.headers as any), + Authorization: `Bearer ${access_token}`, + }, + credentials: 'include', + }); + } + + if (!res.ok) { + throw new Error(`${res.status} Error!!`); + } + await wait(0); + + // 204 No Content 응답 처리 + if (res.status === 204) { + return null; + } + + const data = await res.json(); + return data; +}; + +// function getCookie(name: string) { +// const value = `; ${document.cookie}`; +// const parts = value.split(`; ${name}=`); +// if (parts.length === 2) return parts.pop()?.split(';').shift(); +// } + +export { baseURL, Headers, wait, enableMock, fetchData, fetchAuthData }; diff --git a/src/controllers/common/query.ts b/src/controllers/common/query.ts new file mode 100644 index 00000000..e1cafc29 --- /dev/null +++ b/src/controllers/common/query.ts @@ -0,0 +1,14 @@ +import { STOCK_TYPE } from '@ts/Types'; +import { fetchDescentStocks, fetchHotStocks, fetchRisingStocks } from '../stocks/api'; + +export const queryOptions = { + retry: 5, + staleTime: 1000, +}; + +export const STOCK_FETCH_FUNCTIONS: Record Promise> = { + HOT: fetchHotStocks, + RISING: fetchRisingStocks, + DESCENT: fetchDescentStocks, + RELATED: () => Promise.resolve([]), +}; diff --git a/src/controllers/experiment/api.ts b/src/controllers/experiment/api.ts new file mode 100644 index 00000000..b8e29453 --- /dev/null +++ b/src/controllers/experiment/api.ts @@ -0,0 +1,191 @@ +import { StockCountryKey } from '@ts/StockCountry'; +import { StockSectorKey } from '@ts/StockSector'; +import { ReportClassKey } from '@components/Lab/ReportClassChart/ReportClassChart.Type'; +import { PatternQuadrantKey } from '@components/Lab/ReportPatternChart/ReportPatternChart.Type'; +import { fetchAuthData } from '@controllers/common/base'; +import type { StockInfo } from '@controllers/stocks/types'; + +export type { StockInfo }; + +export interface SectorRecommendStockInfo { + stockId: number; + stockName: string; + price: number; + priceDiff: number; + priceDiffPerCent: number; + score: number; + diff: number; + keywords: string[]; + country: StockCountryKey; +} + +export interface SectorRecommendResponse { + items: SectorRecommendStockInfo[]; + page: number; + size: number; + totalElements: number; + totalPages: number; +} + +export interface BuyExperimentResponse { + success: boolean; + message: string; + price: number; +} + +export type ExperimentStatus = 'COMPLETE' | 'PROGRESS'; + +export interface ExperimentItem { + experimentId: number; + symbolName: string; + buyAt: string; + buyPrice: number; + roi: number; + status: ExperimentStatus; + country: StockCountryKey; + buyScore: number; + currentScore: number; + currentPrice: number; + stockId: number; +} + +export interface ExperimentStatusResponse { + progressExperiments: ExperimentItem[]; + completeExperiments: ExperimentItem[]; + avgRoi: number; + totalTradeCount: number; + progressTradeCount: number; + successRate: number; +} + +export interface ExperimentDetailTradeInfo { + price: number; + score: number; + tradeAt: string; + roi: number; +} + +export interface ExperimentDetailResponse { + symbolName: string; + stockId: number; + roi: number; + status: ExperimentStatus; + tradeInfos: ExperimentDetailTradeInfo[]; + buyScore: number; + currentScore: number; + buyPrice: number; + currentPrice: number; + buyAt: string; + country: StockCountryKey; +} + +export interface ExperimentReportStatisticDto { + scoreRange: string; + totalAvgRoi: number; + userAvgRoi: number; +} + +export interface ExperimentReportPatternDto { + roi: number; + score: number; + buyAt: string; +} + +export interface ExperimentReportResponse { + weeklyExperimentCount: number; + reportStatisticDtos: ExperimentReportStatisticDto[]; + totalUserExperiments: number; + successUserExperiments: number; + sameGradeUserRate: number; + reportPatternDtos: ExperimentReportPatternDto[]; +} + +interface PortfolioResultRecommendScoreTable { + min: number; + max: number; + avgYieldTotal: number; + avgYieldUser: number; +} + +export interface PortfolioResultRecommend { + weeklyExperimentCount: number; + bestYieldScore: number; + worstYieldScore: number; + scoreTable: PortfolioResultRecommendScoreTable[]; +} + +export interface PortfolioResultHumanIndicator { + type: ReportClassKey; + percentile: number; + successRate: number; + totalBuyCount: number; + successCount: number; + distribution: Record; +} + +export interface PortfolioResultPatternHistory { + date: string; + score: number; + roi: number; + stockId: number; + stockName: string; + duplicateName: boolean; +} + +export interface PortfolioResultPattern { + type: PatternQuadrantKey; + percentile: number; + history: PortfolioResultPatternHistory[]; +} + +export interface PortfolioResultResponse { + recommend: PortfolioResultRecommend; + humanIndicator: PortfolioResultHumanIndicator; + pattern: PortfolioResultPattern; +} + +// POST /experiment/{stockId}/buy/{country} +export const fetchBuyExperiment = (stockId: number, country: StockCountryKey): Promise => { + return fetchAuthData(`/experiment/${stockId}/buy/${country}`, { method: 'POST' }); +}; + +// GET /experiment/status +export const fetchExperimentStatus = (): Promise => { + return fetchAuthData('/experiment/status', { method: 'GET' }); +}; + +// GET /experiment/status/{experimentId}/detail +export const fetchExperimentDetail = (experimentId: number): Promise => { + return fetchAuthData(`/experiment/status/${experimentId}/detail`, { method: 'GET' }); +}; + +// GET /experiment/report +export const fetchExperimentReport = (): Promise => { + return fetchAuthData('/experiment/report', { method: 'GET' }); +}; + +// GET /portfolio/result +export const fetchPortfolioResult = (): Promise => { + return fetchAuthData('/portfolio/result', { method: 'GET' }); +}; + +// GET /stock/sector/overseas/{sectorKey}/recommend +// GET /stock/sector/domestic/{sectorKey}/recommend +export const fetchSectorRecommend = async ( + country: StockCountryKey, + sectorKey: StockSectorKey, +): Promise => { + const countryPath = country == 'KOREA' ? 'domestic' : 'overseas'; + + try { + const res: SectorRecommendResponse = await fetchAuthData(`/stock/sector/${countryPath}/${sectorKey}/recommend`); + // 204 No Content인 경우 또는 items가 없는 경우 빈 배열 반환 + if (!res || !res.items || res.items.length === 0) { + return []; + } + return res.items; + } catch (error) { + console.error('fetchSectorRecommend error:', error); + return []; + } +}; diff --git a/src/controllers/experiment/query.ts b/src/controllers/experiment/query.ts new file mode 100644 index 00000000..a7e1b229 --- /dev/null +++ b/src/controllers/experiment/query.ts @@ -0,0 +1,61 @@ +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { StockCountryKey } from '@ts/StockCountry'; +import { StockSectorKey } from '@ts/StockSector'; +import { queryOptions } from '@controllers/common/query'; +import { + fetchBuyExperiment, + fetchExperimentDetail, + fetchExperimentReport, + fetchExperimentStatus, + fetchPortfolioResult, + fetchSectorRecommend, +} from './api'; + +// ----- Queries ----- +export const useExperimentStatusQuery = () => { + return useQuery(['experimentStatus'], fetchExperimentStatus, { + ...queryOptions, + enabled: !!localStorage.getItem('access_token'), + }); +}; + +export const useExperimentDetailQuery = (experimentId: number) => { + return useQuery(['experimentDetail', experimentId], () => fetchExperimentDetail(experimentId), { + ...queryOptions, + enabled: !!experimentId && !!localStorage.getItem('access_token'), + }); +}; + +export const useExperimentReportQuery = () => { + return useQuery(['experimentReport'], fetchExperimentReport, { + ...queryOptions, + enabled: !!localStorage.getItem('access_token'), + }); +}; + +export const usePortfolioResultQuery = () => { + return useQuery(['portfolioResult'], fetchPortfolioResult, { + ...queryOptions, + enabled: !!localStorage.getItem('access_token'), + }); +}; + +// ----- Mutations ----- +export const useBuyExperimentMutation = () => { + const qc = useQueryClient(); + return useMutation( + ({ stockId, country }: { stockId: number; country: StockCountryKey }) => fetchBuyExperiment(stockId, country), + { + onSettled: () => { + qc.invalidateQueries({ queryKey: ['experiment', 'experimentStatus'] }); + }, + }, + ); +}; + +export const useSectorRecommendQuery = (country: StockCountryKey, sectorKey: StockSectorKey | undefined) => { + return useQuery(['sectorRecommend', country, sectorKey], () => fetchSectorRecommend(country, sectorKey!), { + ...queryOptions, + enabled: !!country && !!sectorKey && !!localStorage.getItem('access_token'), + }); +}; diff --git a/src/controllers/notification/api.ts b/src/controllers/notification/api.ts new file mode 100644 index 00000000..81539fc1 --- /dev/null +++ b/src/controllers/notification/api.ts @@ -0,0 +1,42 @@ +import { StockCountryKey } from '@ts/StockCountry'; +import { fetchAuthData } from '@controllers/common/base'; + +export type NotificationType = 'SCORE_SPIKE'; + +export interface NotificationResponse { + id: number; + stockId: number | null; + stockName: string | null; + notificationType: NotificationType; + oldScore: number | null; + newScore: number | null; + changeAbs: number | null; + title: string; + body: string; + isRead: boolean; + createdAt: string; + country: StockCountryKey | null; +} + +export interface NotificationPageResponse { + content: NotificationResponse[]; + totalElements: number; + totalPages: number; + size: number; + number: number; +} + +// GET /notification +export const fetchNotification = (page: number = 0, size: number = 20): Promise => { + return fetchAuthData(`/notification?page=${page}&size=${size}`); +}; + +// GET /notification/unread-count +export const fetchUnreadCount = (): Promise<{ unreadCount: number }> => { + return fetchAuthData(`/notification/unread-count`); +}; + +// PATCH /notification/read/{notificationId} +export const markNotificationAsRead = (notificationId: number): Promise => { + return fetchAuthData(`/notification/read/${notificationId}`, { method: 'PATCH' }); +}; diff --git a/src/controllers/notification/mock.ts b/src/controllers/notification/mock.ts new file mode 100644 index 00000000..c3f21224 --- /dev/null +++ b/src/controllers/notification/mock.ts @@ -0,0 +1,60 @@ +import { NotificationResponse } from './api'; + +export const mockNotifications: NotificationResponse[] = [ + { + id: 1, + stockId: 904, + stockName: '삼성전자', + notificationType: 'SCORE_SPIKE', + oldScore: 85, + newScore: 86, + changeAbs: 1, + title: '인간지표 변동 알림', + body: '인간지표가 1점 올랐어요', + isRead: false, + createdAt: '2025-01-01', + country: 'KOREA', + }, + { + id: 2, + stockId: 89, + stockName: 'NAVER', + notificationType: 'SCORE_SPIKE', + oldScore: 51, + newScore: 50, + changeAbs: 1, + title: '인간지표 변동 알림', + body: '인간지표가 1점 내렸어요', + isRead: false, + createdAt: '2025-01-01', + country: 'KOREA', + }, + { + id: 3, + stockId: 5990, + stockName: '인텔', + notificationType: 'SCORE_SPIKE', + oldScore: 43, + newScore: 44, + changeAbs: 1, + title: '인간지표 변동 알림', + body: '인간지표가 1점 올랐어요', + isRead: false, + createdAt: '2025-01-01', + country: 'OVERSEA', + }, + { + id: 4, + stockId: 2716, + stockName: '한화', + notificationType: 'SCORE_SPIKE', + oldScore: 89, + newScore: 90, + changeAbs: 1, + title: '인간지표 변동 알림', + body: '인간지표가 1점 올랐어요', + isRead: false, + createdAt: '2025-01-01', + country: 'KOREA', + }, +]; diff --git a/src/controllers/notification/query.ts b/src/controllers/notification/query.ts new file mode 100644 index 00000000..0ee5b34f --- /dev/null +++ b/src/controllers/notification/query.ts @@ -0,0 +1,64 @@ +import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from 'react-query'; +import { mapNotificationResponseToItem } from '@utils/notificationMapper'; +import { queryOptions } from '@controllers/common/query'; +import { NotificationPageResponse, fetchNotification, fetchUnreadCount, markNotificationAsRead } from './api'; +import { mockNotifications } from './mock'; + +const PAGE_SIZE = 20; + +// ----- Queries ----- +export const useNotificationsQuery = ({ useMock = false }: { useMock?: boolean }) => { + const result = useInfiniteQuery( + ['notification'], + ({ pageParam = 0 }) => + useMock + ? Promise.resolve({ + content: mockNotifications, + totalElements: mockNotifications.length, + totalPages: 1, + size: PAGE_SIZE, + number: pageParam, + }) + : fetchNotification(pageParam, PAGE_SIZE), + { + ...queryOptions, + getNextPageParam: (lastPage) => { + // 현재 페이지가 마지막 페이지보다 작으면 다음 페이지 번호 반환 + const currentPage = lastPage.number; + const totalPages = lastPage.totalPages; + + if (currentPage + 1 < totalPages) { + return currentPage + 1; + } + return undefined; // 더 이상 페이지 없음 + }, + }, + ); + + return { + ...result, + // 모든 페이지의 content를 flat하게 합쳐서 반환 + notifications: result.data?.pages.flatMap((page) => page.content).map(mapNotificationResponseToItem), + // 전체 개수 + totalElements: result.data?.pages[0]?.totalElements ?? 0, + }; +}; + +export const useUnreadCountQuery = () => { + return useQuery<{ unreadCount: number }>(['unreadCount'], fetchUnreadCount, { + ...queryOptions, + enabled: !!localStorage.getItem('access_token'), + }); +}; + +// ----- Mutations ----- + +export const useMarkAsReadMutation = () => { + const qc = useQueryClient(); + return useMutation((notificationId: number) => markNotificationAsRead(notificationId), { + onSuccess: () => { + qc.invalidateQueries(['notifications']); + qc.invalidateQueries(['unreadCount']); + }, + }); +}; diff --git a/src/controllers/preference/api.ts b/src/controllers/preference/api.ts new file mode 100644 index 00000000..f29d41ee --- /dev/null +++ b/src/controllers/preference/api.ts @@ -0,0 +1,60 @@ +import { StockCountryKey } from '@ts/StockCountry'; +import { fetchAuthData } from '@controllers/common/base'; + +export interface PreferenceStockResponse { + stockId: number; + isBookmarked: boolean; + isNotificationEnabled: boolean; +} + +export interface BookmarkItem { + stockId: number; + name: string; + price: number; + priceDiffPerCent: number; + score: number; + diff: number; + isNotificationOn: boolean; + symbolName: string; + country: StockCountryKey; +} + +// GET /preference/stock/{stockId} +export const fetchStockPreference = (stockId: number): Promise => { + return fetchAuthData(`/preference/stock/${stockId}`); +}; + +// POST /preference/bookmark/{stock_id} +export const fetchAddBookmark = (stockId: number | string) => { + return fetchAuthData(`/preference/bookmark/${stockId}`, { method: 'POST' }); +}; + +// DELETE /preference/bookmark/{stock_id} +export const fetchDeleteBookmark = (stockId: number | string) => { + return fetchAuthData(`/preference/bookmark/${stockId}`, { method: 'DELETE' }); +}; + +// POST /preference/hide/{stock_id} +export const fetchHideStock = (stockId: number | string) => { + return fetchAuthData(`/preference/hide/${stockId}`, { method: 'POST' }); +}; + +// DELETE /preference/hide/{stock_id} +export const fetchUnhideStock = (stockId: number | string) => { + return fetchAuthData(`/preference/hide/${stockId}`, { method: 'DELETE' }); +}; + +// PATCH /preference/notification/toggle/{stock_id} +export const fetchToggleNotification = (stockId: number) => { + return fetchAuthData(`/preference/notification/toggle/${stockId}`, { method: 'PATCH' }); +}; + +// GET /preference/bookmark/list +export const fetchBookmarkList = (): Promise => { + return fetchAuthData(`/preference/bookmark/list`); +}; + +// GET /preference/bookmark/count +export const fetchBookmarkCount = (): Promise => { + return fetchAuthData(`/preference/bookmark/count`); +}; diff --git a/src/controllers/preference/query.ts b/src/controllers/preference/query.ts new file mode 100644 index 00000000..2d7b3ded --- /dev/null +++ b/src/controllers/preference/query.ts @@ -0,0 +1,190 @@ +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { queryOptions } from '@controllers/common/query'; +import { + BookmarkItem, + PreferenceStockResponse, + fetchAddBookmark, + fetchBookmarkCount, + fetchBookmarkList, + fetchDeleteBookmark, + fetchHideStock, + fetchStockPreference, + fetchToggleNotification, + fetchUnhideStock, +} from './api'; + +// ----- Queries ----- +export const useBookmarkListQuery = () => { + return useQuery(['bookmarkList'], fetchBookmarkList, { + ...queryOptions, + enabled: !!localStorage.getItem('access_token'), + }); +}; + +export const useBookmarkCountQuery = () => { + return useQuery(['bookmarkCount'], fetchBookmarkCount, { + ...queryOptions, + enabled: !!localStorage.getItem('access_token'), + }); +}; + +export const useStockPreferenceQuery = (stockId?: number) => { + return useQuery(['stockPreference', stockId], () => fetchStockPreference(stockId!), { + ...queryOptions, + enabled: !!stockId && !!localStorage.getItem('access_token'), + }); +}; + +// ----- Mutations ----- + +export const useAddBookmarkMutation = () => { + const qc = useQueryClient(); + + return useMutation((stockId: number | string) => fetchAddBookmark(stockId), { + // 1) 낙관적 업데이트: UI 즉시 반영 + onMutate: async (stockId) => { + await qc.cancelQueries(['bookmarkList']); + await qc.cancelQueries(['stockPreference', stockId]); + + const previousList = qc.getQueryData(['bookmarkList']); + const previousCount = qc.getQueryData(['bookmarkCount']); + const previousPreference = qc.getQueryData(['stockPreference', stockId]); + + // stockPreference 즉시 업데이트 + qc.setQueryData(['stockPreference', stockId], (old: any) => ({ + ...old, + isBookmarked: true, + })); + + // 카운트 증가 (있다면) + if (typeof previousCount === 'number') { + qc.setQueryData(['bookmarkCount'], previousCount + 1); + } + + // 리스트에 추가 (기본 정보만 - 서버에서 정확한 데이터는 onSettled에서 받아옴) + // 주의: BookmarkItem의 모든 필드를 모르면 부분적으로만 추가 + // qc.setQueryData(['bookmarkList'], (old = []) => [ + // ...old, + // { + // stockId: Number(stockId), + // ...other fields... + // }, + // ]); + + return { previousList, previousCount, previousPreference }; + }, + + // 2) 실패 시 롤백 + onError: (_err, stockId, ctx) => { + if (ctx?.previousList) qc.setQueryData(['bookmarkList'], ctx.previousList); + if (typeof ctx?.previousCount === 'number') qc.setQueryData(['bookmarkCount'], ctx.previousCount); + if (ctx?.previousPreference) qc.setQueryData(['stockPreference', stockId], ctx.previousPreference); + }, + + // 3) 성공/실패 상관없이 서버 정합성 맞추기 + onSettled: (_data, _error, stockId) => { + qc.invalidateQueries({ queryKey: ['bookmarkList'] }); + qc.invalidateQueries({ queryKey: ['bookmarkCount'] }); + qc.invalidateQueries({ queryKey: ['stockPreference', stockId] }); + }, + }); +}; + +export const useDeleteBookmarkMutation = () => { + const qc = useQueryClient(); + + return useMutation((stockId: number | string) => fetchDeleteBookmark(stockId), { + // 1) 낙관적 업데이트: UI 즉시 반영 + onMutate: async (stockId) => { + await qc.cancelQueries(['bookmarkList']); + await qc.cancelQueries(['stockPreference', stockId]); + + const previousList = qc.getQueryData(['bookmarkList']); + const previousCount = qc.getQueryData(['bookmarkCount']); + const previousPreference = qc.getQueryData(['stockPreference', stockId]); + + // 리스트에서 해당 아이템 제거 + qc.setQueryData(['bookmarkList'], (old = []) => old.filter((s) => s.stockId !== Number(stockId))); + + // 카운트도 함께 줄이기(있다면) + if (typeof previousCount === 'number') { + qc.setQueryData(['bookmarkCount'], Math.max(0, previousCount - 1)); + } + + // stockPreference도 즉시 업데이트 + qc.setQueryData(['stockPreference', stockId], (old: any) => ({ + ...old, + isBookmarked: false, + })); + + return { previousList, previousCount, previousPreference }; + }, + + // 2) 실패 시 롤백 + onError: (_err, stockId, ctx) => { + if (ctx?.previousList) qc.setQueryData(['bookmarkList'], ctx.previousList); + if (typeof ctx?.previousCount === 'number') qc.setQueryData(['bookmarkCount'], ctx.previousCount); + if (ctx?.previousPreference) qc.setQueryData(['stockPreference', stockId], ctx.previousPreference); + }, + + // 3) 성공/실패 상관없이 서버 정합성 맞추기 + onSettled: (_data, _error, stockId) => { + qc.invalidateQueries({ queryKey: ['bookmarkList'] }); + qc.invalidateQueries({ queryKey: ['bookmarkCount'] }); + qc.invalidateQueries({ queryKey: ['stockPreference', stockId] }); + }, + }); +}; + +export const useHideStockMutation = () => { + const qc = useQueryClient(); + return useMutation((stockId: number) => fetchHideStock(stockId), { + onSuccess: () => { + // 숨김 리스트를 따로 만들 경우 여기에 invalidate 추가 + qc.invalidateQueries({ queryKey: ['hideStocks'] }); + }, + }); +}; + +export const useUnhideStockMutation = () => { + const qc = useQueryClient(); + return useMutation((stockId: number) => fetchUnhideStock(stockId), { + onSuccess: () => { + // 숨김 리스트를 따로 만들 경우 여기에 invalidate 추가 + qc.invalidateQueries(['hideStocks']); + }, + }); +}; + +export const useToggleNotificationMutation = () => { + const qc = useQueryClient(); + + return useMutation((stockId: number) => fetchToggleNotification(stockId), { + onMutate: async (stockId) => { + await qc.cancelQueries(['stockPreference', stockId]); + const previousPreference = qc.getQueryData(['stockPreference', stockId]); + + // 낙관적 업데이트: 알림 상태 즉시 토글 + qc.setQueryData(['stockPreference', stockId], (old: any) => ({ + ...old, + isNotificationOn: !old?.isNotificationOn, + })); + + qc.setQueryData(['bookmarkList'], (old: any) => + old?.map((e: BookmarkItem) => (e.stockId === stockId ? { ...e, isNotificationOn: !e.isNotificationOn } : e)), + ); + + return { previousPreference }; + }, + onError: (_err, stockId, ctx) => { + // 실패 시 롤백 + if (ctx?.previousPreference) { + qc.setQueryData(['stockPreference', stockId], ctx.previousPreference); + } + }, + onSuccess: (_data, stockId) => { + qc.invalidateQueries(['bookmarkList']); + qc.invalidateQueries(['stockPreference', stockId]); + }, + }); +}; diff --git a/src/controllers/query.ts b/src/controllers/query.ts deleted file mode 100644 index 6252e262..00000000 --- a/src/controllers/query.ts +++ /dev/null @@ -1,362 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useQuery, useQueryClient } from 'react-query'; -import { CHART_MOVING_AVERAGE_COLOR, CHART_PRICE_FIELD } from '@ts/Constants'; -import { STOCK_COUNTRY } from '@ts/Types'; -import { formatDateISO, formatLocalDateToDate } from '@utils/Date'; -import { StockType } from '@components/Common/Common.Type'; -import { - AutoCompleteItem, - IndexScoreInfo, - PERIOD_CODE, - PopularItems, - StockInfo, - StockTableInfo, -} from '@controllers/api.Type'; -import { - fetchDescentStocks, - fetchHotStocks, - fetchIndexScore, - fetchKeywords, - fetchPopularKeywords, - fetchPopularStocks, - fetchRelevant, - fetchRisingStocks, - fetchScore, - fetchSearchKeyword, - fetchSearchSymbolName, - fetchSearchWordCloud, - fetchStockChart, - fetchStockSummary, - fetchStockTable, -} from './api'; -import { StockDetailInfo } from './api.Type'; - -export const queryOptions = { - // retry: 5, // 실패 시 반복 횟수 - 기본 3 - staleTime: 1000, // 다시 fetch 보내려 할때 해당 시간 이내이면 굳이 fetch 다시 하지 않음 -}; - -const STOCK_FETCH_FUNCTIONS = { - HOT: fetchHotStocks, - RISING: fetchRisingStocks, - DESCENT: fetchDescentStocks, -}; - -export const useSymbolNameSearchQuery = (name: string, country: STOCK_COUNTRY) => { - return useQuery( - ['symbolNameSearch', name, country], - () => fetchSearchSymbolName(name, country), - queryOptions, - ); -}; - -export const useHomeStockFetchQuery = (type: StockType, country: string) => { - return useQuery( - ['homeStockFetch', type, country], - () => STOCK_FETCH_FUNCTIONS[type](country), - queryOptions, - ); -}; - -export const useScoreQuery = (id: number, country: string) => { - return useQuery(['score', id, country], () => fetchScore(id, country), queryOptions); -}; - -export const useChartInfoQuery = (id: number, periodCode: PERIOD_CODE, startDate: string) => { - return useQuery( - ['chartInfo', id, periodCode, startDate], - () => fetchStockChart(id, periodCode, startDate, '2025-12-30'), - queryOptions, - ); -}; - -export const useKeywordsQuery = (country: string) => { - return useQuery(['keywords', country], () => fetchKeywords(country), queryOptions); -}; - -export const useStockTableInfoQuery = (category: string, country: string) => { - return useQuery( - ['stockTableInfo', category, country], - () => fetchStockTable(category, country), - queryOptions, - ); -}; - -export const useIndexScoreQuery = () => { - return useQuery(['indexScore'], () => fetchIndexScore(), queryOptions); -}; - -export const useKeywordSearchQuery = (keywordName: string) => { - return useQuery(['keywordSearch', keywordName], () => fetchSearchKeyword(keywordName), queryOptions); -}; - -// SearchTitle - -export const useStockSummaryQuery = (symbol: string, country: STOCK_COUNTRY) => { - const { data = [] } = useQuery( - ['stockSummary', symbol, country], - () => fetchStockSummary(symbol, country), - queryOptions, - ); - - return [data]; -}; - -// SearchRelevant - -export const useRelevantStockFetchQuery = (id: number) => { - const { data } = useQuery(['relevantStockFetch', id], () => fetchRelevant(id), { - ...queryOptions, - enabled: id != undefined, - }); - - return [data]; -}; - -// WordCloud - -const WordCloudWorker = new Worker(new URL('@utils/worker/GenerateWordCloud.ts', import.meta.url), { - type: 'module', -}); - -export const useWordCloudQuery = ( - symbol: string, - country: STOCK_COUNTRY, - { width, height }: any, - isMobile: boolean, -) => { - const [wordCloud, setWordCloud] = useState([]); - - const queryClient = useQueryClient(); - - useEffect(() => { - queryClient.setQueryData(['WordCloudQuery', symbol, 0, 0], []); - WordCloudWorker.onmessage = ({ data: { symbol, layouts, height, width } }: any) => { - queryClient.setQueryData(['WordCloudQuery', symbol, width, height], layouts); - setWordCloud(layouts); - }; - }, []); - - useEffect(() => { - const queryData = queryClient.getQueryData(['WordCloudQuery', symbol, width, height]); - - if (!queryData) { - fetchSearchWordCloud(symbol, country).then((res) => { - WordCloudWorker.postMessage({ - symbol, - data: res, - width, - height, - isMobile, - }); - }); - - return; - } - setWordCloud(queryData); - }, [symbol, width, height]); - - return [wordCloud]; -}; - -// StockChart - -export const useStockChartQuery = (stockId: number, period: string) => { - const [chartData, setChartData] = useState([]); - const [lastDate, setLastDate] = useState(''); - const firstDate = '1970-01-01'; - - const queryClient = useQueryClient(); - - const queryData = queryClient.getQueryData<{ - lastDate: string; - priceInfos: any[]; - chartData: any[]; - }>(['StockChart', stockId, period]); - - const formatChartData = (priceInfos: any[], chartData: any[], length: number) => { - const newChartData = [ - ...Array.from( - { - length: length, - }, - () => ({}), - ), - ...chartData, - ]; - const updateLength = length + Object.keys(CHART_MOVING_AVERAGE_COLOR).reduce((acc, e) => Math.max(acc, ~~e), 0); - - priceInfos.some((e, i, arr) => { - if (i >= updateLength) return; - newChartData[i] = { - date: formatLocalDateToDate(e.localDate), - price: Object.fromEntries( - Object.entries(CHART_PRICE_FIELD).map(([key, value]) => [ - key, - { - value: Number(e[value.key]), - delta: i ? Number(e[value.key]) / Number(arr[i - 1].closePrice) - 1 : 0, - }, - ]), - ), - SMA: Object.fromEntries( - Object.keys(CHART_MOVING_AVERAGE_COLOR).map((range) => [ - range, - { - price: - Array.from( - { - length: Math.min(~~range, i + 1), - }, - (_, j) => Number(arr[i - j].closePrice), - ).reduce((acc, e) => acc + e) / Math.min(~~range, i + 1), - }, - ]), - ), - score: { - value: e.score, - delta: e.diff, - }, - trading: { - value: e.accumulatedTradingValue, - volume: e.accumulatedTradingVolume, - delta: i ? Number(e.accumulatedTradingVolume) / Number(arr[i - 1].accumulatedTradingVolume) - 1 : 0, - }, - }; - }); - - return newChartData; - }; - - const fetchData = async ({ - lastDate, - priceInfos, - chartData, - }: { - lastDate: string; - priceInfos: any[]; - chartData: any[]; - }) => { - fetchStockChart(stockId, period as PERIOD_CODE, firstDate, lastDate).then((e) => { - const newPriceInfos = [...[...e.priceInfos].reverse(), ...priceInfos]; - const lastPriceDate = formatLocalDateToDate(newPriceInfos[0].localDate); - if (['D', 'W'].includes(period)) lastPriceDate.setDate(lastPriceDate.getDate() - 1); - else if (period == 'M') { - lastPriceDate.setMonth(lastPriceDate.getMonth() - 1); - } - const newChartData = formatChartData(newPriceInfos, chartData, e.priceInfos.length); - - queryClient.setQueryData(['StockChart', stockId, period], { - lastDate: formatDateISO(lastPriceDate), - priceInfos: newPriceInfos, - chartData: newChartData, - }); - - setChartData(newChartData); - }); - }; - - useEffect(() => { - if (!queryData) { - fetchData({ - lastDate: formatDateISO(new Date()), - priceInfos: [], - chartData: [], - }); - return; - } - setChartData(queryData.chartData); - }, [stockId, period]); - - useEffect(() => { - if (queryData) fetchData(queryData); - }, [lastDate]); - - return [chartData, setLastDate]; -}; - -// SearchBar - -export const usePopularStockFetchQuery = () => { - const { data = [] } = useQuery( - ['PopularStocksFetch'], - async () => { - const popularStocks = await Promise.resolve(fetchPopularStocks()); - return popularStocks.map((stock) => ({ - ...stock, - value: stock.symbolName, - })) as PopularItems[]; - }, - { - ...queryOptions, - placeholderData: [], - }, - ); - - return [data]; -}; - -export const PopularKeywordQuery = () => { - const { data = [] } = useQuery( - ['PopularKeywordFetch'], - async () => { - const popularKeywords = await Promise.resolve(fetchPopularKeywords()); - return popularKeywords.map((keyword) => ({ - value: keyword, - })) as PopularItems[]; - }, - { - ...queryOptions, - placeholderData: [], - }, - ); - - return [data]; -}; - -export const useAutoComplete = ( - fetchQuery: (input: string) => Promise, - key: string, -): [AutoCompleteItem[], (input: string) => Promise] => { - const [{ results, value }, setSearchState] = useState<{ - value: string; - results: AutoCompleteItem[]; - }>({ - value: '', - results: [], - }); - - const { data = [] } = useQuery(['AutoComplete', value, key], () => results, { - placeholderData: [], - }); - - const queryClient = useQueryClient(); - - const fetchData = async (input: string): Promise => { - const cached = queryClient.getQueryData(['AutoComplete', input, key]); - if (cached || !input.length) { - return setSearchState({ - value: input, - results: cached || [], - }); - } - - await fetchQuery(input) - .then((res) => { - if (!res.length) throw new Error('No results found'); - setSearchState({ - value: input, - results: res.map((item) => ({ - ...item, - value: item[key].toUpperCase(), - })), - }); - }) - .catch((err) => { - err; - }); - return; - }; - - return [data, fetchData]; -}; diff --git a/src/controllers/score/api.ts b/src/controllers/score/api.ts new file mode 100644 index 00000000..5812b803 --- /dev/null +++ b/src/controllers/score/api.ts @@ -0,0 +1,23 @@ +import { enableMock, fetchData } from '@controllers/common/base'; +import { mockIndexScore } from './mock'; + +export type IndexScore = { + kospiVix: number; + kospiVixDiff: number; + kospiIndex: number; + kospiIndexDiff: number; + kosdaqIndex: number; + kosdaqIndexDiff: number; + snpVix: number; + snpVixDiff: number; + snpIndex: number; + snpIndexDiff: number; + nasdaqIndex: number; + nasdaqIndexDiff: number; +}; + +// GET /score/index +export const fetchIndexScore = (): Promise => { + if (enableMock) return Promise.resolve(mockIndexScore); + return fetchData(`/score/index`); +}; diff --git a/src/controllers/score/mock.ts b/src/controllers/score/mock.ts new file mode 100644 index 00000000..f5f6753d --- /dev/null +++ b/src/controllers/score/mock.ts @@ -0,0 +1,16 @@ +import { IndexScore } from './api'; + +export const mockIndexScore: IndexScore = { + kospiVix: 50, + kospiVixDiff: 5, + kospiIndex: 64, + kospiIndexDiff: 23, + kosdaqIndex: 49, + kosdaqIndexDiff: 7, + snpVix: 32, + snpVixDiff: 2, + snpIndex: 47, + snpIndexDiff: -18, + nasdaqIndex: 43, + nasdaqIndexDiff: -3, +}; diff --git a/src/controllers/score/query.ts b/src/controllers/score/query.ts new file mode 100644 index 00000000..1d008319 --- /dev/null +++ b/src/controllers/score/query.ts @@ -0,0 +1,38 @@ +import { useQuery } from 'react-query'; +import { StockCountryKey } from '@ts/StockCountry'; +import { queryOptions } from '@controllers/common/query'; +import { fetchIndexScore } from './api'; + +type ScoreIndex = 'kospiVix' | 'kospiIndex' | 'kosdaqIndex' | 'snpVix' | 'snpIndex' | 'nasdaqIndex'; + +const scoreIndexNames: Record = { + kospiVix: '공포지수', + kospiIndex: '코스피', + kosdaqIndex: '코스닥', + snpVix: '공포지수', + snpIndex: 'S&P 500', + nasdaqIndex: '나스닥', +}; + +const scoreIndexCountry: Record = { + KOREA: ['kospiVix', 'kospiIndex', 'kosdaqIndex'], + OVERSEA: ['snpVix', 'snpIndex', 'nasdaqIndex'], +}; + +export const useIndexScoreQuery = () => { + return useQuery(['indexScore'], () => fetchIndexScore(), { + ...queryOptions, + select: (data) => + Object.fromEntries( + Object.entries(scoreIndexCountry).map(([key, value]) => [ + key, + value.map((e) => ({ + key: e, + name: scoreIndexNames[e], + value: data[e], + diff: data[`${e}Diff`], + })), + ]), + ), + }); +}; diff --git a/src/controllers/shortview/api.ts b/src/controllers/shortview/api.ts new file mode 100644 index 00000000..cda55b17 --- /dev/null +++ b/src/controllers/shortview/api.ts @@ -0,0 +1,19 @@ +import { StockCountryKey } from '@ts/StockCountry'; +import { fetchAuthData } from '@controllers/common/base'; + +//이름 뭐로 하지 +export interface ShortViewItem { + stockId: number; + imageUrl: string; + stockName: string; + price: number; + priceDiff: number; + priceDiffPerCent: number; + score: number; + diff: number; + keywords: string[]; + country: StockCountryKey; +} + +// GET /shortview +export const fetchShortview = (): Promise => fetchAuthData('/shortview'); diff --git a/src/controllers/shortview/mock.ts b/src/controllers/shortview/mock.ts new file mode 100644 index 00000000..f15be80d --- /dev/null +++ b/src/controllers/shortview/mock.ts @@ -0,0 +1,53 @@ +import { getStockImageUrl } from '@utils/stockImage'; +import { ShortViewItem } from './api'; + +export const mockShortViewItems: ShortViewItem[] = [ + { + stockId: 904, + stockName: '삼성전자', + price: 71500, + priceDiff: 1200, + priceDiffPerCent: 1.71, + score: 85, + diff: 79, + country: 'KOREA', + keywords: ['IT', '반도체'], + imageUrl: getStockImageUrl(904), + }, + { + stockId: 89, + stockName: 'NAVER', + price: 205000, + priceDiff: -1500, + priceDiffPerCent: -0.73, + score: 51, + diff: -12, + country: 'KOREA', + keywords: ['플랫폼', '인터넷'], + imageUrl: getStockImageUrl(89), + }, + { + stockId: 5990, + stockName: '인텔', + price: 61000, + priceDiff: 500, + priceDiffPerCent: 0.83, + score: 43, + diff: 5, + country: 'OVERSEA', + keywords: ['모빌리티', '광고'], + imageUrl: getStockImageUrl(5990), + }, + { + stockId: 2716, + stockName: '한화', + price: 83800, + priceDiff: 200, + priceDiffPerCent: 0.24, + score: 89, + diff: 11, + country: 'KOREA', + keywords: ['모빌리티', '광고'], + imageUrl: getStockImageUrl(2716), + }, +]; diff --git a/src/controllers/shortview/query.ts b/src/controllers/shortview/query.ts new file mode 100644 index 00000000..4ac18c25 --- /dev/null +++ b/src/controllers/shortview/query.ts @@ -0,0 +1,178 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useQuery, useQueryClient } from 'react-query'; +import { queryOptions } from '@controllers/common/query'; +import { ShortViewItem, fetchShortview } from './api'; +import { mockShortViewItems } from './mock'; + +const SHORTVIEW_ITEMS_KEY = ['shortview'] as const; +const SHORTVIEW_IDX_KEY = ['shortview', 'currentIdx'] as const; +const SHORTVIEW_SEEN_KEY = ['shortview', 'seen'] as const; + +export const useShortViewQuery = ({ useMock = false }: { useMock?: boolean }) => { + const queryClient = useQueryClient(); + + const [currentIdx, _setCurrentIdx] = useState(() => { + return queryClient.getQueryData(SHORTVIEW_IDX_KEY) ?? 0; + }); + + const setCurrentIdx = useCallback( + (updater: number | ((prev: number) => number), len: number) => { + _setCurrentIdx((prev) => { + const rawNext = typeof updater === 'function' ? (updater as (p: number) => number)(prev) : updater; + const next = Math.max(0, Math.min(rawNext, len)); // ✅ 0~len inclusive + queryClient.setQueryData(SHORTVIEW_IDX_KEY, next); + return next; + }); + }, + [queryClient], + ); + + // ===== items query(초기 로딩 + 캐시 배열 유지) ===== + const enabled = useMock ? true : !!localStorage.getItem('access_token'); + + const query = useQuery( + SHORTVIEW_ITEMS_KEY, + () => (useMock ? mockShortViewItems : fetchShortview()), + { + ...queryOptions, + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: true, + enabled: useMock ? true : !!localStorage.getItem('access_token'), + onSuccess: (data) => { + queryClient.setQueryData(SHORTVIEW_ITEMS_KEY, (prev = []) => { + const seen = new Set(prev.map((x) => x.stockId)); + const next = [...prev]; + + for (const item of data) { + if (seen.has(item.stockId)) continue; + seen.add(item.stockId); + next.push(item); + } + + queryClient.setQueryData>(SHORTVIEW_SEEN_KEY, (prev = new Set()) => { + return new Set([...prev, ...seen]); + }); + + return next; + }); + }, + }, + ); + + const items: ShortViewItem[] = query.data ?? []; + const len = items.length; + + const setNextIndex = useCallback(() => { + setCurrentIdx((prev) => (prev < len ? prev + 1 : prev), len); + }, [len, setCurrentIdx]); + + const setPrevIndex = useCallback(() => { + setCurrentIdx((prev) => (prev > 0 ? prev - 1 : prev), len); + }, [len, setCurrentIdx]); + + const isFetchTarget = len === 0 ? false : currentIdx >= len - 2; + + const removeItem = useCallback(() => { + console.log('remove'); + queryClient.setQueryData(SHORTVIEW_ITEMS_KEY, (prev) => { + const list = prev ? [...prev] : []; + if (list.length === 0) return list; + + const idx = currentIdx; + if (idx < 0 || idx >= list.length) return list; + + list.splice(idx, 1); + return list; + }); + }, [currentIdx, queryClient]); + + const appendItem = useCallback( + (item: ShortViewItem) => { + queryClient.setQueryData(SHORTVIEW_ITEMS_KEY, (prev) => { + const list = prev ? [...prev] : []; + list.splice(currentIdx, 0, item); + + return list; + }); + }, + [currentIdx, queryClient], + ); + + // ===== fetchMore: 최대 10번 재시도, unique 1개라도 나오면 append하고 종료 ===== + const fetchingRef = useRef(false); + const [isFetchingMore, setIsFetchingMore] = useState(false); + const TRYS = 10; + + const fetchMore = useCallback(async () => { + if (!enabled) return { added: 0, tries: 0 }; + if (fetchingRef.current) return { added: 0, tries: 0 }; + + fetchingRef.current = true; + setIsFetchingMore(true); + + try { + let tries = 0; + + while (tries < TRYS) { + tries++; + + const raw = useMock ? mockShortViewItems : await fetchShortview(); + + // 현재 캐시 기준으로 seen 만들기 + const current = queryClient.getQueryData(SHORTVIEW_ITEMS_KEY) ?? []; + const seen = queryClient.getQueryData>(SHORTVIEW_SEEN_KEY) ?? new Set(); + + // 새로 받은 것 중 중복 아닌 것만 남김 + const unique = raw.filter((it) => !seen.has(it.stockId)); + + if (unique.length > 0) { + queryClient.setQueryData(SHORTVIEW_ITEMS_KEY, [...current, ...unique]); + queryClient.setQueryData>(SHORTVIEW_SEEN_KEY, (prev = new Set()) => { + return new Set([...prev, ...unique.map((x) => x.stockId)]); + }); + return { added: unique.length, tries }; + } + + await new Promise((resolve) => setTimeout(resolve, 500)); + } + + return { added: 0, tries: TRYS }; + } finally { + fetchingRef.current = false; + setIsFetchingMore(false); + } + }, [enabled, queryClient, useMock]); + + useEffect(() => { + if (!isFetchTarget) return; + + fetchMore(); + }, [isFetchTarget]); + + const isAtEnd = currentIdx >= items.length - 1; + + // 컴포넌트에서 쓰기 편하게 current item도 제공(선택) + const currentItem = useMemo(() => { + if (currentIdx < 0 || currentIdx >= items.length) return null; + return items[currentIdx]; + }, [currentIdx, items]); + + return { + ...query, + data: items, + + currentIdx, + currentItem, + isAtEnd, + + setNextIndex, + setPrevIndex, + + removeItem, + appendItem, + + fetchMore, // ✅ 버튼으로 강제 호출 + isFetchingMore, // ✅ 버튼 disabled 등에 사용 + }; +}; diff --git a/src/controllers/stocks/api.ts b/src/controllers/stocks/api.ts new file mode 100644 index 00000000..17bc665b --- /dev/null +++ b/src/controllers/stocks/api.ts @@ -0,0 +1,109 @@ +import { WordFrequency } from '@ts/Interfaces'; +import { StockCountryKey } from '@ts/StockCountry'; +import { enableMock, fetchData } from '../common/base'; +import { + fetchChartMock, + fetchKeywordsMock, + fetchPopularStocksMock, + fetchRelevantMock, + fetchScoreCardMock, + fetchScoreMock, + fetchSearchSymbolNameMock, + fetchSearchWordCloudMock, +} from './mock'; +import { + MonthlyAverageResponse, + PERIOD_CODE, + PopularStocks, + SectorAverageResponse, + SectorPercentileResponse, + StockDetailInfo, +} from './types'; + +export const fetchScore = async (id: number, country: string) => { + if (enableMock) return fetchScoreMock; + return fetchData(`/${id}/score/${country}`); +}; + +export const fetchRelevant = async (id: number) => { + if (enableMock) return fetchRelevantMock; + return fetchData(`/stock/${id}/relevant`); +}; + +export const fetchHotStocks = async (country: string) => { + return fetchData(`/stock/hot/${country}`); +}; + +export const fetchRisingStocks = async (country: string) => { + if (enableMock) return fetchScoreCardMock; + return fetchData(`/stock/rising/${country}`); +}; + +export const fetchDescentStocks = async (country: string) => { + if (enableMock) return fetchScoreCardMock; + return fetchData(`/stock/descent/${country}`); +}; + +export const fetchSearchSymbolName = (symbolname: string, country: StockCountryKey): Promise => { + if (enableMock) return Promise.resolve(fetchSearchSymbolNameMock); + return fetchData(`/stock/search/${symbolname}/${country}`); +}; + +export const fetchStockInfo = (stockId: number, country: StockCountryKey) => { + return fetchData(`/stock/${stockId}/info/${country}`); +}; + +export const fetchStockTable = (category: string, country: string) => { + return fetchData(`/stock/category/${category}/${country}`); +}; + +export const fetchStockSummary = (symbol: string, country: StockCountryKey) => { + return fetchData(`/stock/summary/${symbol}/${country}`); +}; + +export const fetchSearchWordCloud = (symbol: string, country: string): Promise => { + if (enableMock) return Promise.resolve(fetchSearchWordCloudMock); + return fetchData(`/wordcloud/${symbol}/${country}`); +}; + +export const fetchStockChart = async (id: number, periodCode: PERIOD_CODE, startDate: string, endDate: string) => { + if (enableMock) return fetchChartMock; + return fetchData(`/stock/${id}/chart/{country}?periodCode=${periodCode}&startDate=${startDate}&endDate=${endDate}`); +}; + +export const fetchAutoComplete = (name: string) => { + return fetchData(`/stock/autocomplete?keyword=${name}`); +}; + +export const fetchPopularStocks = (): Promise => { + if (enableMock) return Promise.resolve(fetchPopularStocksMock as PopularStocks[]); + return fetchData(`/stock/rankings/hot`); +}; + +// keyword + +export const fetchSearchKeyword = (keywordName: string) => { + return fetchData(`/keyword/${keywordName}/stocks`); +}; + +export const fetchKeywordRankings = (): Promise => { + return fetchData('/keyword/rankings'); +}; + +export const fetchPopularKeywords = (country: string): Promise => { + if (enableMock) return Promise.resolve(fetchKeywordsMock); + return fetchData(`/keyword/popular/${country}`); +}; + +export const fetchSectorAverage = (country: string, sector: string): Promise => { + return fetchData(`/stock/sector/average/${country}/${sector}`); +}; + +export const fetchSectorPercentile = (stockId: number): Promise => { + return fetchData(`/stock/${stockId}/sector/percentile`); +}; + +export const fetchMonthlyAverage = (stockId: number, yearMonth?: string): Promise => { + const queryParam = yearMonth ? `?yearMonth=${yearMonth}` : ''; + return fetchData(`/stock/${stockId}/average/month${queryParam}`); +}; diff --git a/src/controllers/mock.ts b/src/controllers/stocks/mock.ts similarity index 99% rename from src/controllers/mock.ts rename to src/controllers/stocks/mock.ts index 0176c96e..f99c3b8f 100644 --- a/src/controllers/mock.ts +++ b/src/controllers/stocks/mock.ts @@ -1,19 +1,4 @@ -import { StockDetailInfo } from './api.Type'; - -export const fetchIndexScoreMock = { - kospiVix: 50, - kospiVixDiff: 5, - kospiIndex: 64, - kospiIndexDiff: 23, - kosdaqIndex: 49, - kosdaqIndexDiff: 7, - snpVix: 32, - snpVixDiff: 2, - snpIndex: 47, - snpIndexDiff: -18, - nasdaqIndex: 43, - nasdaqIndexDiff: -3, -}; +import { StockDetailInfo } from './types'; export const fetchKeywordsMock = [ '이재명', @@ -118,6 +103,9 @@ export const fetchSearchSymbolNameMock: StockDetailInfo = { price: 0, priceDiff: 0, priceDiffPerCent: 0, + score: 60, + scoreDiff: -5, + keywords: ['삼성생명', '내'], }; export const fetchScoreMock = { diff --git a/src/controllers/stocks/query.ts b/src/controllers/stocks/query.ts new file mode 100644 index 00000000..d6cab99e --- /dev/null +++ b/src/controllers/stocks/query.ts @@ -0,0 +1,323 @@ +import { useEffect, useState } from 'react'; +import { useQueries, useQuery, useQueryClient } from 'react-query'; +import { CHART_MOVING_AVERAGE_COLOR, CHART_PRICE_FIELD } from '@ts/Constants'; +import { StockCountryKey } from '@ts/StockCountry'; +import { STOCK_TYPE } from '@ts/Types'; +import { formatDateISO, formatLocalDateToDate } from '@utils/Date'; +import { STOCK_FETCH_FUNCTIONS, queryOptions } from '../common/query'; +import { + fetchAutoComplete, + fetchKeywordRankings, + fetchMonthlyAverage, + fetchPopularKeywords, + fetchPopularStocks, + fetchRelevant, + fetchScore, + fetchSearchKeyword, + fetchSearchSymbolName, + fetchSearchWordCloud, + fetchSectorAverage, + fetchSectorPercentile, + fetchStockChart, + fetchStockInfo, + fetchStockSummary, + fetchStockTable, +} from './api'; +import type { AutoCompleteItem, PopularItems, StockDetailInfo, StockInfo, StockTableInfo } from './types'; +import { PERIOD_CODE } from './types'; + +export const useSymbolNameSearchQuery = (name: string, country: StockCountryKey) => { + return useQuery(['symbolNameSearch', name, country], () => fetchSearchSymbolName(name, country), { + ...queryOptions, + enabled: !!name && !!country, + }); +}; + +export const useStockIdSearchQuery = (stockId: number, country: StockCountryKey) => { + return useQuery(['stockInfo', stockId, country], () => fetchStockInfo(stockId, country), { + ...queryOptions, + enabled: !!stockId && !!country, + }); +}; + +export const stockInfoQueries = (stocks: StockDetailInfo[]) => { + return useQueries( + (stocks ?? []).map((stock) => ({ + queryKey: ['stockInfo', stock.stockId, stock.country], + queryFn: () => fetchStockInfo(stock.stockId, stock.country), // API 함수 직접 사용 + enabled: !!stock, + })), + ); +}; + +export const useHomeStockFetchQuery = (type: STOCK_TYPE, country: StockCountryKey) => { + return useQuery(['homeStockFetch', type, country], () => STOCK_FETCH_FUNCTIONS[type](country), queryOptions); +}; + +export const useScoreQuery = (id: number, country: StockCountryKey) => { + return useQuery(['score', id, country], () => fetchScore(id, country), queryOptions); +}; + +export const useChartInfoQuery = (id: number, periodCode: PERIOD_CODE, startDate: string) => { + return useQuery( + ['chartInfo', id, periodCode, startDate], + () => fetchStockChart(id, periodCode, startDate, '2025-12-30'), + queryOptions, + ); +}; + +export const useStockTableInfoQuery = (category: string, country: string) => { + return useQuery( + ['stockTableInfo', category, country], + () => fetchStockTable(category, country), + queryOptions, + ); +}; + +export const useStockSummaryQuery = (symbol: string, country: StockCountryKey) => { + return useQuery(['stockSummary', symbol, country], () => fetchStockSummary(symbol, country), queryOptions); +}; + +export const useRelevantStockFetchQuery = (id: number) => { + return useQuery(['relevantStockFetch', id], () => fetchRelevant(id), { + ...queryOptions, + enabled: id != undefined, + }); +}; + +const WordCloudWorker = new Worker(new URL('@utils/worker/GenerateWordCloud.ts', import.meta.url), { type: 'module' }); + +export const useWordCloudQuery = ( + symbol: string, + country: StockCountryKey, + { width, height }: any, + isMobile: boolean, +) => { + const [wordCloud, setWordCloud] = useState([]); + const queryClient = useQueryClient(); + + useEffect(() => { + queryClient.setQueryData(['WordCloudQuery', symbol, 0, 0], []); + WordCloudWorker.onmessage = ({ data: { symbol, layouts, height, width } }: any) => { + queryClient.setQueryData(['WordCloudQuery', symbol, width, height], layouts); + setWordCloud(layouts); + }; + }, []); + + useEffect(() => { + const queryData = queryClient.getQueryData(['WordCloudQuery', symbol, width, height]); + if (!queryData) { + fetchSearchWordCloud(symbol, country).then((res) => { + WordCloudWorker.postMessage({ symbol, data: res, width, height, isMobile }); + }); + return; + } + setWordCloud(queryData); + }, [symbol, width, height]); + + return [wordCloud] as const; +}; + +export const useStockChartQuery = (stockId: number, period: PERIOD_CODE) => { + const [chartData, setChartData] = useState([]); + const [lastDate, setLastDate] = useState(''); + const firstDate = '1970-01-01'; + const queryClient = useQueryClient(); + + const queryData = queryClient.getQueryData<{ lastDate: string; priceInfos: any[]; chartData: any[] }>([ + 'StockChart', + stockId, + period, + ]); + + const formatChartData = (priceInfos: any[], chartData: any[], length: number) => { + const newChartData = [...Array.from({ length }, () => ({})), ...chartData]; + const updateLength = length + Object.keys(CHART_MOVING_AVERAGE_COLOR).reduce((acc, e) => Math.max(acc, ~~e), 0); + priceInfos.some((e, i, arr) => { + if (i >= updateLength) return; + newChartData[i] = { + date: formatLocalDateToDate(e.localDate), + price: Object.fromEntries( + Object.entries(CHART_PRICE_FIELD).map(([key, value]) => [ + key, + { value: Number(e[value.key]), delta: i ? Number(e[value.key]) / Number(arr[i - 1].closePrice) - 1 : 0 }, + ]), + ), + SMA: Object.fromEntries( + Object.keys(CHART_MOVING_AVERAGE_COLOR).map((range) => [ + range, + { + price: + Array.from({ length: Math.min(~~range, i + 1) }, (_, j) => Number(arr[i - j].closePrice)).reduce( + (acc, e) => acc + e, + ) / Math.min(~~range, i + 1), + }, + ]), + ), + score: { value: e.score, delta: e.diff }, + trading: { + value: Number(e.accumulatedTradingValue), + volume: Number(e.accumulatedTradingVolume), + delta: i ? Number(e.accumulatedTradingVolume) / Number(arr[i - 1].accumulatedTradingVolume) - 1 : 0, + }, + } as any; + }); + return newChartData; + }; + + const fetchData = async ({ + lastDate, + priceInfos, + chartData, + }: { + lastDate: string; + priceInfos: any[]; + chartData: any[]; + }) => { + fetchStockChart(stockId, period as PERIOD_CODE, firstDate, lastDate).then((e) => { + const newPriceInfos = [...[...e.priceInfos].reverse(), ...priceInfos]; + const lastPriceDate = formatLocalDateToDate(newPriceInfos[0].localDate); + if (['D', 'W'].includes(period)) lastPriceDate.setDate(lastPriceDate.getDate() - 1); + else if (period == 'M') lastPriceDate.setMonth(lastPriceDate.getMonth() - 1); + const newChartData = formatChartData(newPriceInfos, chartData, e.priceInfos.length); + queryClient.setQueryData(['StockChart', stockId, period], { + lastDate: formatDateISO(lastPriceDate), + priceInfos: newPriceInfos, + chartData: newChartData, + }); + setChartData(newChartData); + }); + }; + + useEffect(() => { + if (!queryData) { + fetchData({ lastDate: formatDateISO(new Date()), priceInfos: [], chartData: [] }); + return; + } + setChartData(queryData.chartData); + }, [stockId, period]); + + useEffect(() => { + if (queryData) fetchData(queryData); + }, [lastDate]); + + return [chartData, setLastDate] as const; +}; + +export const usePopularStockFetchQuery = () => { + const { data = [] } = useQuery( + ['popularStocks'], + async () => { + const popularStocks = await Promise.resolve(fetchPopularStocks()); + return popularStocks.map((stock) => ({ ...stock, value: stock.symbolName })) as PopularItems[]; + }, + { ...queryOptions, placeholderData: [] }, + ); + return [data] as const; +}; + +export const useAutoComplete = ( + fetchQuery: (input: string) => Promise, + key: string, +): [AutoCompleteItem[], (input: string) => Promise] => { + const [{ results, value }, setSearchState] = useState<{ value: string; results: AutoCompleteItem[] }>({ + value: '', + results: [], + }); + + const { data = [] } = useQuery(['AutoComplete', value, key], () => results, { + placeholderData: [], + }); + + const queryClient = useQueryClient(); + + const fetchData = async (input: string): Promise => { + const cached = queryClient.getQueryData(['AutoComplete', input, key]); + if (cached || !input.length) { + return setSearchState({ value: input, results: cached || [] }); + } + await fetchQuery(input) + .then((res) => { + if (!res.length) throw new Error('No results found'); + setSearchState({ value: input, results: res.map((item) => ({ ...item, value: item[key].toUpperCase() })) }); + }) + .catch(() => { + // Error handling - silently fail + }); + return; + }; + + return [data, fetchData]; +}; + +const useDebounce = (value: T, delay: number): T => { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => clearTimeout(timer); + }, [value, delay]); + + return debouncedValue; +}; + +export const useAutoCompleteStockQuery = (input: string) => { + const value = input.trim(); + + const debouncedValue = useDebounce(value, 250); + + return useQuery(['autocomplete', debouncedValue], () => fetchAutoComplete(debouncedValue), { + ...queryOptions, + staleTime: 60000, + cacheTime: 10000, + enabled: !!debouncedValue, + keepPreviousData: true, + }); +}; + +export const useKeywordSearchQuery = (keywordName: string) => { + return useQuery(['keywordSearch', keywordName], () => fetchSearchKeyword(keywordName), queryOptions); +}; + +export const usePopularKeywordsQuery = (country: string) => { + return useQuery(['popularKeywords', country], () => fetchPopularKeywords(country), { + ...queryOptions, + placeholderData: [], + }); +}; + +export const useKeywordRankingsQuery = () => { + return useQuery(['keywordRankings'], () => fetchKeywordRankings(), { + ...queryOptions, + placeholderData: [], + }); +}; + +export const useStockZipyoDataQuery = (stockId: number, country: StockCountryKey) => { + return useQuery( + ['stockZipyoData', stockId, country], + async () => { + const [sectorPercentile, monthlyAverage] = await Promise.all([ + fetchSectorPercentile(stockId), + fetchMonthlyAverage(stockId), + ]); + + const sectorAverage = await fetchSectorAverage(country, sectorPercentile.sector); + + return { + industryType: sectorPercentile.sector, + industryName: sectorPercentile.sectorName, + industryAverage: sectorAverage.averageScore, + stockRanking: sectorPercentile.topPercent, + monthlyAverage: monthlyAverage.averageScore, + }; + }, + { + ...queryOptions, + enabled: !!stockId && !!country, + }, + ); +}; diff --git a/src/controllers/api.Type.ts b/src/controllers/stocks/types.ts similarity index 52% rename from src/controllers/api.Type.ts rename to src/controllers/stocks/types.ts index 2345a96f..d6c44823 100644 --- a/src/controllers/api.Type.ts +++ b/src/controllers/stocks/types.ts @@ -1,4 +1,4 @@ -import { STOCK_COUNTRY } from '@ts/Types'; +import { StockCountryKey } from '@ts/StockCountry'; export interface StockDetailInfo { stockId: number; @@ -6,11 +6,15 @@ export interface StockDetailInfo { symbolName: string; securityName: string; exchangeNum: string; - country: STOCK_COUNTRY; + country: StockCountryKey; price: number; priceDiff: number; priceDiffPerCent: number; + + score: number; + scoreDiff: number; + keywords: string[]; } export interface StockInfo { @@ -22,7 +26,7 @@ export interface StockInfo { } export interface StockTableInfo { - stockId: string; + stockId: number; symbolName: string; country: string; price: number; @@ -57,10 +61,19 @@ export interface AutoCompleteItem { symbolName: string; keywordNames: string[]; keyword: string; - country: STOCK_COUNTRY; + country: StockCountryKey; value: string; } +export interface AutoCompleteStockItem { + stockId: number; + symbolName: string; + score: number; + diff: number; + country: StockCountryKey; + keywords?: string[]; +} + export interface SearchBarResultItems extends PopularItems, AutoCompleteItem { value: string; } @@ -73,3 +86,48 @@ export interface PopularStocks { } export type PERIOD_CODE = 'D' | 'W' | 'M'; + +// Deprecated types - not used anymore +export interface FavoriteStock { + stockId: number; + name: string; + price: number; + priceDiffPerCent: number; + score: number; + diff: number; + isNotificationOn: boolean; + isSelected?: boolean; + symbolName: string; + country: StockCountryKey; +} + +export interface StockPreferenceStatus { + isBookmarked: boolean; + isNotificationOn: boolean; +} + +export interface SectorAverageResponse { + sector: string; + sectorName: string; + averageScore: number; + count: number; +} + +export interface SectorPercentileResponse { + stockId: number; + sector: string; + sectorName: string; + score: number; + rank: number; + total: number; + topPercent: number; +} + +export interface MonthlyAverageResponse { + stockId: number; + symbolName: string; + country: StockCountryKey; + yearMonth: string; + dataCount: number; + averageScore: number; +} diff --git a/src/hooks/useAuthInfo.ts b/src/hooks/useAuthInfo.ts new file mode 100644 index 00000000..11c8fc2a --- /dev/null +++ b/src/hooks/useAuthInfo.ts @@ -0,0 +1,60 @@ +import { useNavigate } from 'react-router-dom'; +import { webPath } from '@router/index'; +import useLocalStorageState from './useLocalStorageState'; + +interface UserInfo { + email: string; + nickname: string; + profileImage?: string; + provider: string; +} + +interface LoginOptions { + returnState?: unknown; // 로그인 후 돌아갈 때 복원할 React Router state +} + +const useAuthInfo = () => { + const navigate = useNavigate(); + const [beforeLoginDepth, setBeforeLoginDepth] = useLocalStorageState('before_login_depth'); + const [accessToken, setAccessToken, removeAccessToken] = useLocalStorageState('access_token'); + const [, setRefreshToken, removeRefreshToken] = useLocalStorageState('refresh_token'); + const [userInfo, _setUserInfo, removeUserInfo] = useLocalStorageState('user_info'); + const isLogin = !!accessToken; + + const handleNavigateLogin = (options?: LoginOptions) => { + setBeforeLoginDepth(window.history.length); + + // 현재 경로 저장 (OAuth 리다이렉트 후에도 복원 가능하도록) + const returnPath = window.location.pathname + window.location.search; + sessionStorage.setItem('login_return_path', returnPath); + + // React Router state 저장 (명시적으로 전달받아야 함) + if (options?.returnState !== undefined) { + sessionStorage.setItem('login_return_state', JSON.stringify(options.returnState)); + } else { + sessionStorage.removeItem('login_return_state'); + } + + navigate(webPath.login); + }; + + const setUserInfo = (newUserInfo: Partial) => { + _setUserInfo({ ...(userInfo || {}), ...newUserInfo } as UserInfo); + }; + + const setAuthInfo = (accessToken: string, refreshToken: string, userInfo: UserInfo) => { + setAccessToken(accessToken); + setRefreshToken(refreshToken); + setUserInfo(userInfo); + }; + + const clearAuthInfo = () => { + removeAccessToken(); + removeRefreshToken(); + removeUserInfo(); + }; + + return { isLogin, userInfo, beforeLoginDepth, handleNavigateLogin, setUserInfo, setAuthInfo, clearAuthInfo }; +}; + +export default useAuthInfo; diff --git a/src/hooks/useCanvas.ts b/src/hooks/useCanvas.ts index fd4c3bb9..2fc6f94d 100644 --- a/src/hooks/useCanvas.ts +++ b/src/hooks/useCanvas.ts @@ -1,12 +1,12 @@ -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; const useCanvas = (setCanvas: (canvas: HTMLCanvasElement) => void) => { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; - canvas && setCanvas(canvas); - }, []); + if (canvas) setCanvas(canvas); + }, [setCanvas]); return canvasRef; }; diff --git a/src/hooks/useHiDPICanvas.ts b/src/hooks/useHiDPICanvas.ts new file mode 100644 index 00000000..10336f80 --- /dev/null +++ b/src/hooks/useHiDPICanvas.ts @@ -0,0 +1,54 @@ +import { useLayoutEffect, useReducer, useRef, useState } from 'react'; +import { CanvasSize, resizeHiDPICanvas } from '@utils/Canvas'; + +const useHiDPICanvas = (opts?: { + canvasRef?: React.RefObject; + onResize?: (size: CanvasSize) => void; +}): { + canvasRef: React.RefObject; + canvasSize: CanvasSize; // CSS px + redrawTick: number; // backing store 리사이즈 발생 시 증가 +} => { + const innerRef = useRef(null); + const canvasRef = opts?.canvasRef ?? innerRef; + const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 }); + const [redrawTick, bumpRedrawTick] = useReducer((x) => x + 1, 0); + + const onResizeRef = useRef(opts?.onResize); + onResizeRef.current = opts?.onResize; + + // 1) CSS 크기 추적 + useLayoutEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ro = new ResizeObserver((entries) => { + for (const entry of entries) { + const width = entry.contentRect.width; + const height = entry.contentRect.height; + + setCanvasSize((prev) => (prev.width === width && prev.height === height ? prev : { width, height })); + } + }); + + ro.observe(canvas); + return () => ro.disconnect(); + }, []); + + // 2) backing store 리사이즈 + redrawTick + useLayoutEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + if (!canvasSize.width || !canvasSize.height) return; + + const resized = resizeHiDPICanvas(canvas, canvasSize); + if (!resized) return; + + bumpRedrawTick(); + onResizeRef.current?.(canvasSize); + }, [canvasSize.width, canvasSize.height]); + + return { canvasRef, canvasSize, redrawTick }; +}; + +export default useHiDPICanvas; diff --git a/src/hooks/useLocalStorageState.ts b/src/hooks/useLocalStorageState.ts new file mode 100644 index 00000000..26aece35 --- /dev/null +++ b/src/hooks/useLocalStorageState.ts @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react'; + +type LocalStorageKey = + | 'access_token' + | 'refresh_token' + | 'user_info' + | 'before_login_depth' + | 'recent_stocks' + | 'tutorial_watched_shortview' + | 'recent_provider' + | 'last_visit_page' + | 'app_install_popup_last_shown'; + +const useLocalStorageState = ( + key: LocalStorageKey, + initialValue?: T, +): [T | undefined, (value: T) => void, () => void] => { + const readValue = (): T | undefined => { + const storedValue = localStorage.getItem(key); + if (!storedValue) { + return initialValue; + } + try { + return JSON.parse(storedValue) as T; + } catch { + // JSON 파싱 실패 시 string으로 간주 + return storedValue as T; + } + }; + + const [state, setState] = useState(readValue); + + // localStorage 변경 감지 + useEffect(() => { + const handleStorageChange = (e: StorageEvent) => { + if (e.key === key) { + setState(readValue()); + } + }; + + // 같은 탭에서의 변경사항을 감지하기 위한 커스텀 이벤트 + const handleCustomStorageChange = (e: CustomEvent) => { + if (e.detail.key === key) { + setState(readValue()); + } + }; + + window.addEventListener('storage', handleStorageChange); + window.addEventListener('localStorageChange' as any, handleCustomStorageChange); + + return () => { + window.removeEventListener('storage', handleStorageChange); + window.removeEventListener('localStorageChange' as any, handleCustomStorageChange); + }; + }, [key]); + + const setLocalStorageState = (value: T) => { + setState(value); + localStorage.setItem(key, typeof value === 'string' ? value : JSON.stringify(value)); + // 같은 탭에서 변경사항을 알리기 위한 커스텀 이벤트 발생 + window.dispatchEvent(new CustomEvent('localStorageChange', { detail: { key } })); + }; + + const removeLocalStorageState = () => { + setState(undefined); + localStorage.removeItem(key); + // 같은 탭에서 변경사항을 알리기 위한 커스텀 이벤트 발생 + window.dispatchEvent(new CustomEvent('localStorageChange', { detail: { key } })); + }; + + return [state, setLocalStorageState, removeLocalStorageState]; +}; + +export default useLocalStorageState; diff --git a/src/hooks/useModal.Style.ts b/src/hooks/useModal.Style.ts new file mode 100644 index 00000000..567083d7 --- /dev/null +++ b/src/hooks/useModal.Style.ts @@ -0,0 +1,19 @@ +import styled from '@emotion/styled'; + +const ModalOverlay = styled.div({ + position: 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + background: 'rgba(0, 0, 0, 0.5)', + zIndex: 1000, + padding: '40px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + boxSizing: 'border-box', + backdropFilter: 'blur(4px)', +}); + +export { ModalOverlay }; diff --git a/src/hooks/useModal.tsx b/src/hooks/useModal.tsx new file mode 100644 index 00000000..46fa2206 --- /dev/null +++ b/src/hooks/useModal.tsx @@ -0,0 +1,35 @@ +import { useRef, useState } from 'react'; +import { ModalOverlay } from './useModal.Style'; + +const useModal = ({ Component }: { Component: ({ onClose }: { onClose: () => void }) => React.ReactNode }) => { + const [isOpen, setIsOpen] = useState(false); + const modalRef = useRef(null); + + const openModal = () => { + setIsOpen(true); + }; + + const closeModal = () => { + setIsOpen(false); + }; + + const handleClickOutside = (e: React.MouseEvent) => { + if (modalRef.current && e.target == modalRef.current) { + closeModal(); + } + }; + + const Modal = () => { + if (!isOpen) return null; + + return ( + + + + ); + }; + + return { Modal, openModal }; +}; + +export default useModal; diff --git a/src/hooks/usePreventScroll.ts b/src/hooks/usePreventScroll.ts new file mode 100644 index 00000000..4a3a5d9c --- /dev/null +++ b/src/hooks/usePreventScroll.ts @@ -0,0 +1,48 @@ +import { RefObject, useEffect } from 'react'; + +/** + * body 스크롤을 막는 훅 + * @param isActive - 스크롤 방지 활성화 여부 + * @param scrollableRef - 스크롤을 허용할 요소의 ref (모달 내부 등) + */ +const usePreventScroll = (isActive: boolean = true, scrollableRef?: RefObject) => { + useEffect(() => { + if (!isActive) return; + + const preventScroll = (e: WheelEvent | TouchEvent) => { + // scrollableRef가 있고, 이벤트 타겟이 해당 요소 내부라면 스크롤 허용 + if (scrollableRef?.current?.contains(e.target as Node)) { + return; + } + e.preventDefault(); + }; + + const preventKeyScroll = (e: KeyboardEvent) => { + // 스크롤 관련 키: 방향키, 스페이스, Page Up/Down, Home, End + const scrollKeys = [32, 33, 34, 35, 36, 37, 38, 39, 40]; + + if (scrollableRef?.current?.contains(e.target as Node)) { + return; + } + + if (scrollKeys.includes(e.keyCode)) { + e.preventDefault(); + } + }; + + // passive: false 옵션으로 preventDefault() 동작 보장 + const options: AddEventListenerOptions = { passive: false }; + + window.addEventListener('wheel', preventScroll, options); + window.addEventListener('touchmove', preventScroll, options); + window.addEventListener('keydown', preventKeyScroll, false); + + return () => { + window.removeEventListener('wheel', preventScroll); + window.removeEventListener('touchmove', preventScroll); + window.removeEventListener('keydown', preventKeyScroll); + }; + }, [isActive, scrollableRef]); +}; + +export default usePreventScroll; diff --git a/src/hooks/useQueryComponent.tsx b/src/hooks/useQueryComponent.tsx index 8b3626da..12197126 100644 --- a/src/hooks/useQueryComponent.tsx +++ b/src/hooks/useQueryComponent.tsx @@ -3,8 +3,8 @@ import { UseQueryResult } from 'react-query'; import ErrorComponent from '@components/Common/ErrorComponent'; import LoadingComponent from '@components/Common/LoadingComponent'; -export const useQueryComponent = ({ query }: { query: UseQueryResult }) => { - const { data, isLoading, isError }: { data: any; isLoading: boolean; isError: boolean } = query; +export const useQueryComponent = ({ query }: { query: UseQueryResult }) => { + const { data, isLoading, isError } = query; const [isDeferred, setIsDeferred] = useState(false); @@ -15,8 +15,8 @@ export const useQueryComponent = ({ query }: { query: UseQueryResult }) => { return () => clearTimeout(timeoutId); }, []); - if (isLoading) return [null, isDeferred && ]; - if (isError) return [null, ]; + if (isLoading) return [null, isDeferred && ] as const; + if (isError) return [null, ] as const; - return [data]; + return [data] as const; }; diff --git a/src/hooks/useRafCanvasDraw.ts b/src/hooks/useRafCanvasDraw.ts new file mode 100644 index 00000000..0fe23a1c --- /dev/null +++ b/src/hooks/useRafCanvasDraw.ts @@ -0,0 +1,69 @@ +import { useCallback, useLayoutEffect, useRef } from 'react'; + +interface UseRafCanvasDrawOptions { + dpr?: number; + lineCap?: CanvasLineCap; + lineJoin?: CanvasLineJoin; + lineDash?: number[]; +} + +const useRafCanvasDraw = ( + canvasRef: React.RefObject, + draw: (ctx: CanvasRenderingContext2D) => void, + deps: React.DependencyList, + options: UseRafCanvasDrawOptions = {}, +): (() => void) => { + const { dpr = window.devicePixelRatio || 1, lineCap = 'round', lineJoin = 'round', lineDash = [] } = options; + + const rafIdRef = useRef(null); + const needsDrawRef = useRef(false); + + const doDraw = useCallback(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + + ctx.lineCap = lineCap; + ctx.lineJoin = lineJoin; + ctx.setLineDash(lineDash); + + draw(ctx); + }, [canvasRef, draw, dpr, lineCap, lineJoin, lineDash]); + + const scheduleDraw = useCallback(() => { + needsDrawRef.current = true; + + if (rafIdRef.current != null) return; + + rafIdRef.current = requestAnimationFrame(() => { + rafIdRef.current = null; + + if (!needsDrawRef.current) return; + needsDrawRef.current = false; + + doDraw(); + }); + }, [doDraw]); + + useLayoutEffect(() => { + scheduleDraw(); + + return () => { + if (rafIdRef.current != null) { + cancelAnimationFrame(rafIdRef.current); + } + rafIdRef.current = null; + needsDrawRef.current = false; + }; + }, [scheduleDraw, ...deps]); + + return scheduleDraw; +}; + +export default useRafCanvasDraw; diff --git a/src/hooks/useRecentStocks.ts b/src/hooks/useRecentStocks.ts new file mode 100644 index 00000000..34dc124a --- /dev/null +++ b/src/hooks/useRecentStocks.ts @@ -0,0 +1,31 @@ +import { StockCountryKey } from '@ts/StockCountry'; +import useLocalStorageState from './useLocalStorageState'; + +interface RecentStocks { + symbolName: string; + country: StockCountryKey; +} + +const useRecentStocks = () => { + const [_recentStocks, setRecentStocks] = useLocalStorageState('recent_stocks', []); + const recentStocks = _recentStocks ?? []; + + const addRecentStock = (symbolName: string, country: StockCountryKey) => { + setRecentStocks([ + { symbolName, country }, + ...recentStocks.filter((item: { symbolName: string }) => item.symbolName !== symbolName), + ]); + }; + + const removeRecentStock = (symbolName: string) => { + setRecentStocks(recentStocks.filter((item: { symbolName: string }) => item.symbolName !== symbolName)); + }; + + return { + recentStocks, + addRecentStock, + removeRecentStock, + }; +}; + +export default useRecentStocks; diff --git a/src/hooks/useSnapIndex.ts b/src/hooks/useSnapIndex.ts new file mode 100644 index 00000000..07fdc361 --- /dev/null +++ b/src/hooks/useSnapIndex.ts @@ -0,0 +1,99 @@ +import { useEffect, useState } from 'react'; + +export const useSnapIndex = ( + rootRef: React.RefObject, + opts?: { horizontal?: boolean; threshold?: number }, +) => { + const [index, setIndex] = useState(0); + const horizontal = opts?.horizontal ?? true; + const threshold = opts?.threshold ?? 0.7; + + useEffect(() => { + const rootEl = rootRef.current; + if (!rootEl) return; + + // 컨테이너 내부의 스냅 대상들 + const items = Array.from(rootEl.children); + if (items.length === 0) return; + + // threshold를 단일값로 쓰면 간혹 튀므로, 구간을 조금 나눠 안정화 + const thresholds = Array.from({ length: 6 }, (_, i) => i / 5); // [0, .2, .4, .6, .8, 1] + + // rootMargin으로 중앙 근처에 가중치를 주고 싶다면 아래 값 미세조정 + // (가로 스냅 기준: 좌우 마진을 조금 깎아서 중앙 근처일 때만 intersecting되게) + const margin = horizontal ? '0px -20% 0px -20%' : '-20% 0px -20% 0px'; + + const io = new IntersectionObserver( + (entries) => { + // 현재 보이는(=isIntersecting) 아이템들 중 "가장 많이 보이는" 것을 고름 + let bestIdx = index; + let bestRatio = -1; + + entries.forEach((entry) => { + if (!entry.isIntersecting) return; + const ratio = entry.intersectionRatio; + const i = items.indexOf(entry.target as HTMLDivElement); + if (ratio > bestRatio) { + bestRatio = ratio; + bestIdx = i; + } + }); + + if (bestRatio >= threshold && bestIdx !== index) { + setIndex(bestIdx); + } + }, + { + root: rootEl, + // 중앙 가중치가 필요 없으면 rootMargin: "0px" 로 두세요 + rootMargin: margin, + threshold: thresholds, + }, + ); + + items.forEach((el) => io.observe(el)); + return () => io.disconnect(); + }, [rootRef, horizontal, threshold]); + + // 스크롤이 딱 멈춘 시점에 마지막으로 한 번 더 정밀 계산하고 싶다면 아래 보정 추가 + useEffect(() => { + const rootEl = rootRef.current; + if (!rootEl) return; + + let t: number | null = null; + const onScroll = () => { + if (t) window.clearTimeout(t); + t = window.setTimeout(() => { + // 중앙과 가장 가까운 아이템으로 보정 + const items = Array.from(rootEl.children); + if (items.length === 0) return; + + const rootRect = rootEl.getBoundingClientRect(); + const center = horizontal ? rootRect.left + rootRect.width / 2 : rootRect.top + rootRect.height / 2; + + let closest = 0; + let minDist = Infinity; + + items.forEach((el, i) => { + const r = el.getBoundingClientRect(); + const p = horizontal ? r.left + r.width / 2 : r.top + r.height / 2; + const dist = Math.abs(p - center); + if (dist < minDist) { + minDist = dist; + closest = i; + } + }); + + setIndex(closest); + }, 80); // scrollend 근사 + }; + + rootEl.addEventListener('scroll', onScroll, { passive: true }); + return () => { + rootEl.removeEventListener('scroll', onScroll); + if (t) window.clearTimeout(t); + }; + }, [rootRef, horizontal]); + + return { index }; +}; diff --git a/src/hooks/useSocialAuth.ts b/src/hooks/useSocialAuth.ts new file mode 100644 index 00000000..506bbf5e --- /dev/null +++ b/src/hooks/useSocialAuth.ts @@ -0,0 +1,244 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { safeRandomUUID } from '@utils/random'; +import { webPath } from '@router/index'; +import { type ProviderKey, fetchOAuth2Login } from '@controllers/auth/api'; +import { AUTH_CONFIGS, type SocialProvider, URL_SCHEME } from '../config/oauth'; +import { MESSAGE_TYPES } from '../config/webview'; +import useAuthInfo from './useAuthInfo'; +import useLocalStorageState from './useLocalStorageState'; + +interface OAuthState { + csrf: string; + isWebView: boolean; + fromWebView?: boolean; + provider?: string; + timestamp?: number; +} + +const createOAuthURL = (provider: SocialProvider, state: string): string => { + const config = AUTH_CONFIGS[provider]; + + // Google은 커스텀 스킴을 지원하지 않으므로 항상 HTTPS redirect_uri 사용 + // WebView에서도 HTTPS로 리다이렉트 후, 웹 페이지에서 커스텀 스킴으로 재리다이렉트 + const redirectUri = config.redirectUri; + + const params = new URLSearchParams({ + client_id: config.clientId, + redirect_uri: redirectUri, + response_type: config.responseType, + state: state, // URLSearchParams가 자동으로 인코딩하므로 encodeURIComponent 제거 + ...(config.scope && { scope: config.scope }), + ...(config.accessType && { access_type: config.accessType }), + ...(config.prompt && { prompt: config.prompt }), + ...(config.includeGrantedScopes && { include_granted_scopes: config.includeGrantedScopes }), + ...(config.responseMode && { response_mode: config.responseMode }), + }); + + return `${config.endpoint}?${params.toString()}`; +}; + +export const useSocialAuth = () => { + const navigate = useNavigate(); + const location = useLocation(); + const { setAuthInfo, clearAuthInfo } = useAuthInfo(); + const [, setRecentProvider] = useLocalStorageState('recent_provider'); + + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const isWebView = !!(window as any).ReactNativeWebView; + + const signInWithOAuth = useCallback( + async (provider: SocialProvider) => { + const stateObj: OAuthState = { + csrf: safeRandomUUID(), + isWebView, + fromWebView: isWebView, + provider: provider, + timestamp: Date.now(), + }; + + const state = btoa(JSON.stringify(stateObj)); + localStorage.setItem('oauth_state', state); + + const url = createOAuthURL(provider, state); + + if (isWebView && (window as any).ReactNativeWebView) { + (window as any).ReactNativeWebView.postMessage( + JSON.stringify({ + type: MESSAGE_TYPES.OPEN_EXTERNAL_BROWSER, + provider: provider, + url, + }), + ); + } else { + window.location.href = url; + } + }, + [isWebView], + ); + + const handleOAuthCallback = useCallback( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async (code: string, provider: string, _state: string) => { + clearAuthInfo(); + setIsLoading(true); + setError(null); + + try { + // API는 state로 redirect URI의 base64 인코딩 값을 기대합니다 + const redirectUri = window.location.origin + `/login/oauth2/code/${provider}`; + const apiState = btoa(redirectUri); + const res = await fetchOAuth2Login(code, apiState, provider as ProviderKey); + + if (res.state === 'NEED_REGISTER') { + // WebView에서는 네이티브에 메시지 전송 + if (isWebView && (window as any).ReactNativeWebView) { + (window as any).ReactNativeWebView.postMessage( + JSON.stringify({ + type: MESSAGE_TYPES.NEED_REGISTER, + email: res.email, + provider, + }), + ); + return; + } + + // 브라우저에서는 회원가입 페이지로 이동 + navigate(webPath.register, { + state: { + provider, + email: res.email, + }, + }); + return; + } + + // 로그인 성공 - WebView와 브라우저 모두 동일하게 처리 + setAuthInfo(res.access_token, res.refresh_token, { + email: res.email, + nickname: res.nickname, + profileImage: res.profileImageUrl, + provider: res.provider, + }); + setRecentProvider(provider); + + // WebView인 경우 네이티브 앱에도 알림 + if (isWebView && (window as any).ReactNativeWebView) { + (window as any).ReactNativeWebView.postMessage( + JSON.stringify({ + type: MESSAGE_TYPES.TOKEN, + token: res.access_token, + }), + ); + } + + // 저장된 return path로 이동 + const savedReturnPath = sessionStorage.getItem('login_return_path'); + const returnStateStr = sessionStorage.getItem('login_return_state'); + const returnState = returnStateStr ? JSON.parse(returnStateStr) : undefined; + + sessionStorage.removeItem('login_return_path'); + sessionStorage.removeItem('login_return_state'); + + setIsLoading(false); + + if (savedReturnPath) { + navigate(savedReturnPath, { replace: true, state: returnState }); + } else { + navigate('/', { replace: true }); + } + } catch (error: any) { + console.error('OAuth callback error:', error); + console.error('Error message:', error?.message); + console.error('Error stack:', error?.stack); + console.error('Error response:', error?.response?.data); + setError('로그인에 실패했습니다. 다시 시도해주세요.'); + setIsLoading(false); + } + }, + [isWebView, location.pathname, navigate, setAuthInfo, setRecentProvider, clearAuthInfo], + ); + + const handleWebViewMessage = useCallback( + (event: MessageEvent) => { + try { + const { type, data } = JSON.parse(event.data); + + if (type === MESSAGE_TYPES.AUTH_SUCCESS) { + handleOAuthCallback(data.code, data.provider, data.state || ''); + } else if (type === MESSAGE_TYPES.AUTH_ERROR) { + console.error('OAuth auth error:', data.error); + setError('로그인에 실패했습니다. 다시 시도해주세요.'); + setIsLoading(false); + } + } catch (error) { + console.error('Error parsing web message:', error); + } + }, + [handleOAuthCallback], + ); + + // URL에서 OAuth 콜백 처리 + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const code = params.get('code'); + const error = params.get('error'); + const stateParam = params.get('state'); + + if (error) { + console.error('OAuth error:', error); + const parsedState: OAuthState = stateParam ? JSON.parse(atob(stateParam)) : {}; + + if (parsedState?.fromWebView) { + window.location.href = `${URL_SCHEME}?error=${encodeURIComponent(error)}`; + } else { + setError('로그인에 실패했습니다. 다시 시도해주세요.'); + setIsLoading(false); + } + return; + } + + if (code && stateParam) { + try { + const parsedState: OAuthState = JSON.parse(atob(stateParam)); + + if (parsedState?.fromWebView) { + // WebView에서 온 경우 Deep Link로 복귀 (state도 함께 전달) + window.location.href = `${URL_SCHEME}?code=${encodeURIComponent(code)}&provider=${parsedState.provider || ''}&state=${encodeURIComponent(stateParam || '')}`; + } else { + // 브라우저에서 온 경우: URL 파라미터 제거 후 처리하여 무한 루프 방지 + const provider = location.pathname.split('/').at(-1); + if (provider) { + // URL에서 쿼리 파라미터 제거 + window.history.replaceState({}, '', location.pathname); + setIsLoading(true); + handleOAuthCallback(code, provider, stateParam); + } + } + } catch (error) { + console.error('Error parsing OAuth state:', error); + setError('로그인 처리 중 오류가 발생했습니다.'); + setIsLoading(false); + } + } + }, [location.pathname, location.search, handleOAuthCallback]); + + // WebView 메시지 리스너 등록 + useEffect(() => { + if (!isWebView) { + return; + } + + window.addEventListener('message', handleWebViewMessage); + document.addEventListener('message', handleWebViewMessage as EventListener); + + return () => { + window.removeEventListener('message', handleWebViewMessage); + document.removeEventListener('message', handleWebViewMessage as EventListener); + }; + }, [isWebView, handleWebViewMessage]); + + return { signInWithOAuth, isLoading, error }; +}; diff --git a/src/hooks/useStateRef.ts b/src/hooks/useStateRef.ts new file mode 100644 index 00000000..5868e1da --- /dev/null +++ b/src/hooks/useStateRef.ts @@ -0,0 +1,14 @@ +import { useRef, useState } from 'react'; + +const useStateRef = (initializer: T): [T, (value: T) => void, React.MutableRefObject] => { + const [state, setState] = useState(initializer); + const ref = useRef(state); + + const setValue = (value: T) => { + setState((ref.current = value)); + }; + + return [state, setValue, ref]; +}; + +export default useStateRef; diff --git a/src/hooks/useToast.tsx b/src/hooks/useToast.tsx new file mode 100644 index 00000000..49a996f2 --- /dev/null +++ b/src/hooks/useToast.tsx @@ -0,0 +1,73 @@ +import { useEffect, useRef, useState } from 'react'; + +export interface ToastState { + message: React.ReactNode; + enabled: boolean; + closing: boolean; + duration: number; +} + +const useToast = (defaultDuration: number = 1500) => { + const [toast, setToast] = useState({ + message: null, + enabled: false, + closing: false, + duration: defaultDuration, + }); + + const toastTimeoutRef = useRef(null); + const fadeTimeoutRef = useRef(null); + + const showToast = (message: React.ReactNode, duration: number = defaultDuration) => { + // 기존 타이머들 정리 + [toastTimeoutRef, fadeTimeoutRef].forEach((ref) => { + if (ref.current) clearTimeout(ref.current); + }); + + setToast({ message, enabled: true, closing: true, duration }); + + fadeTimeoutRef.current = setTimeout(() => { + setToast((prev) => ({ ...prev, closing: false })); + }, 0); + + // 페이드아웃 시작 (duration - 0.3초 전) + fadeTimeoutRef.current = setTimeout(() => { + setToast((prev) => ({ ...prev, closing: true })); + }, duration - 300); + + // 토스트 완전히 숨김 + toastTimeoutRef.current = setTimeout(() => { + setToast((prev) => ({ ...prev, enabled: false, closing: false })); + }, duration); + }; + + const hideToast = () => { + if (toast.closing) return; + + [toastTimeoutRef, fadeTimeoutRef].forEach((ref) => { + if (ref.current) clearTimeout(ref.current); + }); + setToast((prev) => ({ ...prev, closing: true })); + + fadeTimeoutRef.current = setTimeout(() => { + setToast((prev) => ({ ...prev, enabled: false })); + }, 300); + }; + + // 컴포넌트 언마운트 시 타이머 정리 + useEffect(() => { + return () => { + [toastTimeoutRef, fadeTimeoutRef].forEach((ref) => { + if (ref.current) clearTimeout(ref.current); + }); + }; + }, []); + + return { + toast, + showToast, + hideToast, + }; +}; + +export default useToast; diff --git a/src/hooks/useWorker.ts b/src/hooks/useWorker.ts index b9d15b3f..4da3dddb 100644 --- a/src/hooks/useWorker.ts +++ b/src/hooks/useWorker.ts @@ -1,5 +1,5 @@ // useWorker.ts -import { useEffect, useState, useRef, useCallback } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; type UseWorkerResult = [result: any, postMessage: (message: any) => void]; diff --git a/src/index.css b/src/index.css index 2c201ae4..a039f123 100644 --- a/src/index.css +++ b/src/index.css @@ -15,7 +15,6 @@ } :root { - color: rgba(255, 255, 255, 0.87); color-scheme: light dark; font-weight: 400; line-height: 1.5; @@ -32,6 +31,7 @@ -o-text-size-adjust: 100%; /* 오페라 구버전 */ -webkit-overflow-scrolling: touch; + overflow-x: hidden; } @@ -43,15 +43,17 @@ body { } */ html { - margin: 0px; zoom: 1; /* 기본 줌 설정 */ + margin: 0px; } body { display: flex; - place-items: center; + justify-items: center; margin: 0px; - background-color: '#0000FF'; + background-color: rgba(255, 255, 255, 0.2); min-width: 320px; min-height: 100vh; + /* overflow: hidden; */ + overscroll-behavior: contain; } diff --git a/src/layout/BottomNavigation/BottomNavigation.Style.ts b/src/layout/BottomNavigation/BottomNavigation.Style.ts new file mode 100644 index 00000000..7b419ce1 --- /dev/null +++ b/src/layout/BottomNavigation/BottomNavigation.Style.ts @@ -0,0 +1,29 @@ +import styled from '@emotion/styled'; +import { theme } from '@styles/themes'; + +const NavContainer = styled('nav')({ + display: 'flex', + position: 'fixed', + bottom: 0, + width: '100%', + maxWidth: '1280px', + backgroundColor: theme.colors.sub_black, + justifyContent: 'space-between', + alignItems: 'center', + padding: '20px 33px 30px', + boxSizing: 'border-box', + zIndex: '10', + boxShadow: '0px -2px 10px 0px rgba(255, 255, 255, 0.05)', +}); + +const NavItem = styled('div')<{ isActive: boolean }>(({ isActive }) => ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + cursor: 'pointer', + transition: 'color 0.2s ease', + + '& svg path': { transition: 'fill 0.2s ease', fill: isActive ? theme.colors.sub_white : '' }, +})); + +export { NavContainer, NavItem }; diff --git a/src/layout/BottomNavigation/BottomNavigation.tsx b/src/layout/BottomNavigation/BottomNavigation.tsx new file mode 100644 index 00000000..3b385390 --- /dev/null +++ b/src/layout/BottomNavigation/BottomNavigation.tsx @@ -0,0 +1,58 @@ +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { webPath } from '@router/index'; +import FavoritesSVG from '@assets/bottomNav/favorites.svg?react'; +import HomeSVG from '@assets/bottomNav/home.svg?react'; +import LabSVG from '@assets/bottomNav/lab.svg?react'; +import MyPageSVG from '@assets/bottomNav/myPage.svg?react'; +import ShortViewSVG from '@assets/bottomNav/shortView.svg?react'; +import { NavContainer, NavItem } from './BottomNavigation.Style'; + +const BottomNavigation = () => { + const location = useLocation(); + const navigate = useNavigate(); + const navItems = [ + { label: '홈', icon: , path: '/' }, + { label: '관심', icon: , path: webPath.favorites }, + { label: '숏뷰', icon: , path: webPath.shortView }, + { label: '실험실', icon: , path: webPath.lab }, + { label: 'My', icon: , path: webPath.mypage }, + ]; + const currentPath = location.pathname; + const activeIndex = navItems.findIndex((item) => item.path === currentPath); + const [isModalOpen, setIsModalOpen] = useState(false); + + useEffect(() => { + const observer = new MutationObserver(() => { + setIsModalOpen(document.body.classList.contains('modal-open')); + }); + + observer.observe(document.body, { + attributes: true, + attributeFilter: ['class'], + }); + + // 초기 상태 반영 + setIsModalOpen(document.body.classList.contains('modal-open')); + + return () => observer.disconnect(); + }, []); + + if (isModalOpen) return null; + + const handleClick = (path: string) => { + navigate(path); + }; + + return ( + + {navItems.map((item, index) => ( + handleClick(item.path)}> + {item.icon} + + ))} + + ); +}; + +export default BottomNavigation; diff --git a/src/layout/Footer/Footer.Style.ts b/src/layout/Footer/Footer.Style.ts deleted file mode 100644 index 5560c1c0..00000000 --- a/src/layout/Footer/Footer.Style.ts +++ /dev/null @@ -1,109 +0,0 @@ -import styled from '@emotion/styled'; -import { media, theme } from '@styles/themes'; - -const FooterContainer = styled.div({ - background: theme.colors.primary50, - width: '100%', -}); - -const FooterContents = styled.div({ - display: 'flex', - flexDirection: 'column', - width: '100%', - maxWidth: '1280px', - padding: '60px', - boxSizing: 'border-box', - margin: '0 auto', - height: '100%', - gap: '32px', - fontSize: '17px', - fontWeight: '700', - - [media[0]]: { - gap: '24px', - padding: '60px 20px', - fontSize: '13px', - }, -}); - -const FooterTitle = styled.div({ - display: 'flex', - alignItems: 'center', - lineHeight: '1', - fontSize: '40px', - color: theme.colors.primary0, - gap: '18px', - - ['svg']: { - width: 'auto', - height: '36px', - }, - - [media[0]]: { - gap: '12px', - fontSize: '32px', - - ['svg']: { - height: '28px', - }, - }, -}); - -const FooterButtonContainer = styled.div({ - display: 'flex', - flexWrap: 'wrap', - gap: '12px', - - [media[0]]: { - gap: '8px', - }, -}); - -const FooterButtonItemContainer = styled.div({ - display: 'flex', - background: theme.colors.primary0, - padding: '12px 18px', - borderRadius: '8px', - gap: '8px', - alignItems: 'center', - color: theme.colors.primary100, - fontSize: '18px', - cursor: 'pointer', - - ['img']: { - height: '1.5em', - }, - - '&:hover': { - background: theme.colors.cornflowerblue, - color: 'white', - transition: '.1s', - }, - - [media[0]]: { - gap: '8px', - width: '100%', - borderRadius: '8px', - }, -}); - -const FooterIconsContainer = styled.div({ - display: 'flex', - gap: '12px', - alignItems: 'center', - fontSize: '32px', - ['svg']: { - cursor: 'pointer', - height: '1em', - width: '1em', - fill: 'white', - }, -}); -export { - FooterContainer, - FooterContents, - FooterTitle, - FooterButtonContainer, - FooterButtonItemContainer, - FooterIconsContainer, -}; diff --git a/src/layout/Footer/Footer.tsx b/src/layout/Footer/Footer.tsx deleted file mode 100644 index 06ef6fce..00000000 --- a/src/layout/Footer/Footer.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { ImgDiv } from '@components/Common/Common'; -import businessSVG from '@assets/footer/footer_business.svg'; -import commentSVG from '@assets/footer/footer_comment.svg'; -import developerSVG from '@assets/footer/footer_developer.svg'; -import dictSVG from '@assets/footer/footer_dict.svg'; -import InstagramSVG from '@assets/footer/footer_instagram.svg?react'; -import LinkedInSVG from '@assets/footer/footer_linkedin.svg?react'; -import ThreadsSVG from '@assets/footer/footer_threads.svg?react'; -import LogoSVG from '@assets/logo_white.svg?react'; -import { - FooterButtonContainer, - FooterButtonItemContainer, - FooterContainer, - FooterContents, - FooterIconsContainer, - FooterTitle, -} from './Footer.Style'; - -const FooterButton = ({ - img, - str, - onClick, -}: { - img: string; - str: string; - onClick: (event: React.MouseEvent) => void; -}) => { - return ( - - - {str} - - ); -}; - -const openDictionaryModel = () => { - window.open('https://balanced-bun-351.notion.site/17412e0c80b880259849c17228046794?pvs=4'); -}; - -const openSurveyWindow = () => { - window.open('https://forms.gle/G8cvb7RqtPjGhxvK7'); -}; - -const openBusinessProposal = () => { - window.open('mailto:humanzipyo2024@gmail.com?cc=anyany3151@naver.com'); -}; - -const openCreatorsInfoModal = () => { - window.open('https://balanced-bun-351.notion.site/crew-17412e0c80b8807ab157fb40648f33dd?pvs=4'); -}; - -const openInstagram = () => { - window.open('https://www.instagram.com/humanzipyo/'); -}; - -const openLinkedIn = () => { - window.open('https://www.linkedin.com/company/humanzipyo'); -}; - -const openThreads = () => { - window.open('https://www.threads.net/@humanzipyo'); -}; - -const Footer = () => { - return ( - <> - - - - About - - - - - - - - - - - - - - - - - ); -}; - -export default Footer; diff --git a/src/layout/Header/Header.Style.ts b/src/layout/Header/Header.Style.ts index 73e51abd..14b329ff 100644 --- a/src/layout/Header/Header.Style.ts +++ b/src/layout/Header/Header.Style.ts @@ -23,9 +23,9 @@ const HeaderLogo = styled.div({ display: 'flex', alignItems: 'center', boxSizing: 'border-box', - + background: theme.colors.primary50, [media[0]]: { - padding: '24px', + padding: '10px', }, ['> svg']: { diff --git a/src/layout/Header/Header.tsx b/src/layout/Header/Header.tsx index ef531ec2..2c53c39d 100644 --- a/src/layout/Header/Header.tsx +++ b/src/layout/Header/Header.tsx @@ -1,24 +1,78 @@ +import styled from '@emotion/styled'; import { useNavigate } from 'react-router-dom'; -import DisquietVote from '@components/Event/Disquiet'; -import SearchBar from '@components/SearchBar/SearchBar'; -import LogoSVG from '@assets/logo_white.svg?react'; -import { HeaderContainer, HeaderContents, HeaderLogo } from './Header.Style'; +import { webPath } from '@router/index'; +import { theme } from '@styles/themes'; +import ArrowLeftSVG from '@assets/arrowLeft.svg?react'; -const Header = () => { +const HeaderContainer = styled.div({ + paddingBottom: '8px', + borderBottom: `4px solid ${theme.colors.sub_gray11}`, +}); + +const HeaderContents = styled.div({ + position: 'relative', + display: 'flex', + alignItems: 'center', + padding: '8px 20px', + gap: '12px', + + ['>svg']: { + width: '32px', + height: 'auto', + aspectRatio: '1 / 1', + cursor: 'pointer', + fill: theme.colors.sub_gray5, + }, + + ['>p']: { + position: 'absolute', + left: '50%', + transform: 'translateX(-50%)', + ...theme.font.body18Semibold, + color: theme.colors.sub_white, + margin: '0', + }, +}); + +const Header = ({ location, onBefore }: { location: string; onBefore?: () => void }) => { const navigate = useNavigate(); + const headerTitle = { + [webPath.register]: '회원가입', + [webPath.registerDone]: '회원가입', + [webPath.editProfile]: '내 정보 수정', + [webPath.editProfileDone]: '내 정보 수정', + [webPath.about]: '인간지표란?', + [webPath.usage]: 'PWA 사용방법', + [webPath.notification]: '알림', + [webPath.labRecordSheet]: '매수 기록지', + ['searchBar']: '검색', + ['labTutorial']: '실험실 소개', + ['labPurchase']: '포트폴리오 생성하기', + }; + + const handleBefore = () => { + if (onBefore) { + onBefore(); + } else { + navigate(-1); + } + }; + + // OAuth 콜백 경로는 동적 파라미터를 포함하므로 startsWith로 확인 + const isOAuthCallback = location.startsWith('/login/oauth2/code/'); + const title = isOAuthCallback ? '로그인' : headerTitle[location as keyof typeof headerTitle]; + // const title = '로그인'; + + if (!title) return null; + return ( - <> - - - navigate('/')}> - - {false && } - - - - - + + + +

    {title}

    +
    +
    ); }; diff --git a/src/layout/Mainlayout/Mainlayout.Style.ts b/src/layout/Mainlayout/Mainlayout.Style.ts index f6a6a501..82936809 100644 --- a/src/layout/Mainlayout/Mainlayout.Style.ts +++ b/src/layout/Mainlayout/Mainlayout.Style.ts @@ -1,5 +1,4 @@ import styled from '@emotion/styled'; -import { theme } from '@styles/themes'; // 추후에 반응형 수정 @@ -7,10 +6,23 @@ const StyledMainlayout = styled.div({ display: 'flex', flexDirection: 'column', alignItems: 'center', - justifyContent: 'space-between', width: '100%', - height: '100vh', - backgroundColor: theme.colors.grayscale100, + minHeight: '100dvh', + background: '#101010', + maxWidth: '1280px', }); -export { StyledMainlayout }; +const MainContent = styled.div( + ({ isNavActive }: { isNavActive: boolean }) => ({ + marginBottom: isNavActive ? '96px' : '0px', + }), + { + position: 'relative', + width: '100%', + flexGrow: '1', + display: 'flex', + flexDirection: 'column', + }, +); + +export { StyledMainlayout, MainContent }; diff --git a/src/layout/Mainlayout/Mainlayout.tsx b/src/layout/Mainlayout/Mainlayout.tsx index ff6593a5..c7ff1b7f 100644 --- a/src/layout/Mainlayout/Mainlayout.tsx +++ b/src/layout/Mainlayout/Mainlayout.tsx @@ -1,14 +1,36 @@ -import Footer from '../Footer/Footer'; -import Header from '../Header/Header'; +import { useLocation } from 'react-router-dom'; +import { detectPWA, detectPlatform, detectWebView } from '@utils/Detector'; +import { webPath } from '@router/index'; +import BottomNavigation from '@layout/BottomNavigation/BottomNavigation'; +import Header from '@layout/Header/Header'; +import AppInstallPopUp from '@components/PopUp/AppInstallPopUp/AppInstallPopUp'; +import PWAInfoPopUp from '@components/PopUp/PWAinfoPopUp/PWAInfoPopUp'; import { LayoutProps } from './Mainlayout.Props'; -import { StyledMainlayout } from './Mainlayout.Style'; +import { MainContent, StyledMainlayout } from './Mainlayout.Style'; const Mainlayout = ({ children }: LayoutProps) => { + const location = useLocation(); + const platform = detectPlatform(); + const isMobileDevice = platform === 'iOS' || platform === 'Android'; + const visiblePWAInfoPopUp = false; + const isRootPage = location.pathname === '/'; + + const isBottomNavigationVisible = ( + ['login', 'register', 'editProfile', 'withdraw', 'term', 'usage', 'notification'] as (keyof typeof webPath)[] + ).reduce((acc, path) => { + return acc && !location.pathname.startsWith(webPath[path]); + }, true); + return ( -
    - {children} -