diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 000000000..66e80ab4b --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,29 @@ +module.exports = { + root: true, + env: { + browser: true, + es2021: true, + node: true, + }, + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + ecmaFeatures: { jsx: true }, + }, + plugins: ['react', 'react-hooks'], + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:prettier/recommended', + 'prettier', + ], + rules: { + 'react/react-in-jsx-scope': 'off', + 'no-console': 'warn', + }, + settings: { + react: { version: 'detect' }, + }, + ignorePatterns: ['node_modules/', 'dist/'], +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..aab466a28 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "useTabs": false, + "tabWidth": 2, + "printWidth": 80, + "arrowParens": "always" +} \ No newline at end of file diff --git a/index.advanced.html b/index.advanced.html index a070c3355..2c6367976 100644 --- a/index.advanced.html +++ b/index.advanced.html @@ -27,6 +27,6 @@
- + \ No newline at end of file diff --git a/index.html b/index.html index da107a22e..ef9281653 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,6 @@
- + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..0e7a232dd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5517 @@ +{ + "name": "front-6th-chapter2-1", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "front-6th-chapter2-1", + "version": "0.0.0", + "dependencies": { + "@testing-library/react": "^16.3.0" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/user-event": "^14.6.1", + "@types/react": "^19.1.9", + "@types/react-dom": "^19.1.7", + "@vitest/ui": "^3.2.4", + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.3", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "jsdom": "^26.1.0", + "prettier": "^3.6.2", + "vite": "^7.0.5", + "vitest": "^3.2.4" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz", + "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" + }, + "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/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.0.tgz", + "integrity": "sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.0.tgz", + "integrity": "sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.0.tgz", + "integrity": "sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.0.tgz", + "integrity": "sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.0.tgz", + "integrity": "sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.0.tgz", + "integrity": "sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.0.tgz", + "integrity": "sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.0.tgz", + "integrity": "sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.0.tgz", + "integrity": "sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.0.tgz", + "integrity": "sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.0.tgz", + "integrity": "sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.0.tgz", + "integrity": "sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.0.tgz", + "integrity": "sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.0.tgz", + "integrity": "sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.0.tgz", + "integrity": "sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.0.tgz", + "integrity": "sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.0.tgz", + "integrity": "sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.0.tgz", + "integrity": "sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.0.tgz", + "integrity": "sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.0.tgz", + "integrity": "sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.4.tgz", + "integrity": "sha512-xDXgLjVunjHqczScfkCJ9iyjdNOVHvvCdqHSSxwM9L0l/wHkTRum67SDc020uAlCoqktJplgO2AAQeLP1wgqDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz", + "integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz", + "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.4.tgz", + "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.1", + "tinyglobby": "^0.2.14", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "3.2.4" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "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", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "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", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/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/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "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-errors": "^1.3.0", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "license": "MIT", + "peer": true + }, + "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/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "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.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "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": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.3.tgz", + "integrity": "sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "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.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.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.9", + "object.fromentries": "^2.0.8", + "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.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "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", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "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": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "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-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/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "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", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "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, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", + "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "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": ">= 0.4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.21", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", + "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "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.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "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.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "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/prop-types/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT", + "peer": true + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "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", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.0.tgz", + "integrity": "sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.0", + "@rollup/rollup-android-arm64": "4.46.0", + "@rollup/rollup-darwin-arm64": "4.46.0", + "@rollup/rollup-darwin-x64": "4.46.0", + "@rollup/rollup-freebsd-arm64": "4.46.0", + "@rollup/rollup-freebsd-x64": "4.46.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.0", + "@rollup/rollup-linux-arm-musleabihf": "4.46.0", + "@rollup/rollup-linux-arm64-gnu": "4.46.0", + "@rollup/rollup-linux-arm64-musl": "4.46.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.0", + "@rollup/rollup-linux-ppc64-gnu": "4.46.0", + "@rollup/rollup-linux-riscv64-gnu": "4.46.0", + "@rollup/rollup-linux-riscv64-musl": "4.46.0", + "@rollup/rollup-linux-s390x-gnu": "4.46.0", + "@rollup/rollup-linux-x64-gnu": "4.46.0", + "@rollup/rollup-linux-x64-musl": "4.46.0", + "@rollup/rollup-win32-arm64-msvc": "4.46.0", + "@rollup/rollup-win32-ia32-msvc": "4.46.0", + "@rollup/rollup-win32-x64-msvc": "4.46.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT", + "peer": true + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "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.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "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.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "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.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", + "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index 121aab60d..a441a6612 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,26 @@ "start:advanced": "vite serve --open ./index.advanced.html", "test": "vitest", "test:basic": "vitest basic.test.js", - "test:advanced": "vitest advanced.test.js", + "test:advanced": "vitest advanced.test.ts", "test:ui": "vitest --ui" }, "devDependencies": { "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", + "@types/react": "^19.1.9", + "@types/react-dom": "^19.1.7", "@vitest/ui": "^3.2.4", + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.3", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", "jsdom": "^26.1.0", + "prettier": "^3.6.2", "vite": "^7.0.5", "vitest": "^3.2.4" + }, + "dependencies": { + "@testing-library/react": "^16.3.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 49a2140ea..000000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,1547 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - '@testing-library/jest-dom': - specifier: ^6.6.3 - version: 6.6.3 - '@testing-library/user-event': - specifier: ^14.6.1 - version: 14.6.1(@testing-library/dom@10.4.0) - '@vitest/ui': - specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4) - jsdom: - specifier: ^26.1.0 - version: 26.1.0 - vite: - specifier: ^7.0.5 - version: 7.0.5 - vitest: - specifier: ^3.2.4 - version: 3.2.4(@vitest/ui@3.2.4)(jsdom@26.1.0) - -packages: - - '@adobe/css-tools@4.4.3': - resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==} - - '@asamuzakjp/css-color@3.2.0': - resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - - '@babel/runtime@7.27.6': - resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} - engines: {node: '>=6.9.0'} - - '@csstools/color-helpers@5.0.2': - resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} - engines: {node: '>=18'} - - '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-color-parser@3.0.10': - resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} - - '@esbuild/aix-ppc64@0.25.8': - resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.8': - resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.8': - resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.8': - resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.8': - resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.8': - resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.8': - resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.8': - resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.8': - resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.8': - resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.8': - resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.8': - resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.8': - resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.8': - resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.8': - resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.8': - resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.8': - resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.8': - resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.8': - resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.8': - resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.8': - resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.8': - resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.25.8': - resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.8': - resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.8': - resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.8': - resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@jridgewell/sourcemap-codec@1.5.4': - resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - - '@polka/url@1.0.0-next.29': - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - - '@rollup/rollup-android-arm-eabi@4.45.1': - resolution: {integrity: sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.45.1': - resolution: {integrity: sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.45.1': - resolution: {integrity: sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.45.1': - resolution: {integrity: sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.45.1': - resolution: {integrity: sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.45.1': - resolution: {integrity: sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.45.1': - resolution: {integrity: sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.45.1': - resolution: {integrity: sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.45.1': - resolution: {integrity: sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.45.1': - resolution: {integrity: sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.45.1': - resolution: {integrity: sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.45.1': - resolution: {integrity: sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.45.1': - resolution: {integrity: sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.45.1': - resolution: {integrity: sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.45.1': - resolution: {integrity: sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.45.1': - resolution: {integrity: sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.45.1': - resolution: {integrity: sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.45.1': - resolution: {integrity: sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.45.1': - resolution: {integrity: sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.45.1': - resolution: {integrity: sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==} - cpu: [x64] - os: [win32] - - '@testing-library/dom@10.4.0': - resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} - engines: {node: '>=18'} - - '@testing-library/jest-dom@6.6.3': - resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==} - engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - - '@testing-library/user-event@14.6.1': - resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} - engines: {node: '>=12', npm: '>=6'} - peerDependencies: - '@testing-library/dom': '>=7.21.4' - - '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - - '@vitest/ui@3.2.4': - resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==} - peerDependencies: - vitest: 3.2.4 - - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - chai@5.2.1: - resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} - engines: {node: '>=18'} - - chalk@3.0.0: - resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} - engines: {node: '>=8'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - css.escape@1.5.1: - resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - - cssstyle@4.6.0: - resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} - engines: {node: '>=18'} - - data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} - engines: {node: '>=18'} - - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - - dom-accessibility-api@0.6.3: - resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} - - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - esbuild@0.25.8: - resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} - engines: {node: '>=18'} - hasBin: true - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} - engines: {node: '>=18'} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - - jsdom@26.1.0: - resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} - engines: {node: '>=18'} - peerDependencies: - canvas: ^3.0.0 - peerDependenciesMeta: - canvas: - optional: true - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - loupe@3.2.0: - resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - nwsapi@2.2.20: - resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} - - parse5@7.3.0: - resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - - pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - - rollup@4.45.1: - resolution: {integrity: sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - sirv@3.0.1: - resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} - engines: {node: '>=18'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} - engines: {node: '>=14.0.0'} - - tldts-core@6.1.86: - resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} - - tldts@6.1.86: - resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} - hasBin: true - - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - - tough-cookie@5.1.2: - resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} - engines: {node: '>=16'} - - tr46@5.1.1: - resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} - engines: {node: '>=18'} - - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - vite@7.0.5: - resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} - - webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - - whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} - - whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - - whatwg-url@14.2.0: - resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} - engines: {node: '>=18'} - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} - - xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - -snapshots: - - '@adobe/css-tools@4.4.3': {} - - '@asamuzakjp/css-color@3.2.0': - dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - lru-cache: 10.4.3 - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/helper-validator-identifier@7.27.1': {} - - '@babel/runtime@7.27.6': {} - - '@csstools/color-helpers@5.0.2': {} - - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/color-helpers': 5.0.2 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-tokenizer@3.0.4': {} - - '@esbuild/aix-ppc64@0.25.8': - optional: true - - '@esbuild/android-arm64@0.25.8': - optional: true - - '@esbuild/android-arm@0.25.8': - optional: true - - '@esbuild/android-x64@0.25.8': - optional: true - - '@esbuild/darwin-arm64@0.25.8': - optional: true - - '@esbuild/darwin-x64@0.25.8': - optional: true - - '@esbuild/freebsd-arm64@0.25.8': - optional: true - - '@esbuild/freebsd-x64@0.25.8': - optional: true - - '@esbuild/linux-arm64@0.25.8': - optional: true - - '@esbuild/linux-arm@0.25.8': - optional: true - - '@esbuild/linux-ia32@0.25.8': - optional: true - - '@esbuild/linux-loong64@0.25.8': - optional: true - - '@esbuild/linux-mips64el@0.25.8': - optional: true - - '@esbuild/linux-ppc64@0.25.8': - optional: true - - '@esbuild/linux-riscv64@0.25.8': - optional: true - - '@esbuild/linux-s390x@0.25.8': - optional: true - - '@esbuild/linux-x64@0.25.8': - optional: true - - '@esbuild/netbsd-arm64@0.25.8': - optional: true - - '@esbuild/netbsd-x64@0.25.8': - optional: true - - '@esbuild/openbsd-arm64@0.25.8': - optional: true - - '@esbuild/openbsd-x64@0.25.8': - optional: true - - '@esbuild/openharmony-arm64@0.25.8': - optional: true - - '@esbuild/sunos-x64@0.25.8': - optional: true - - '@esbuild/win32-arm64@0.25.8': - optional: true - - '@esbuild/win32-ia32@0.25.8': - optional: true - - '@esbuild/win32-x64@0.25.8': - optional: true - - '@jridgewell/sourcemap-codec@1.5.4': {} - - '@polka/url@1.0.0-next.29': {} - - '@rollup/rollup-android-arm-eabi@4.45.1': - optional: true - - '@rollup/rollup-android-arm64@4.45.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.45.1': - optional: true - - '@rollup/rollup-darwin-x64@4.45.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.45.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.45.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.45.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.45.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.45.1': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.45.1': - optional: true - - '@rollup/rollup-linux-loongarch64-gnu@4.45.1': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.45.1': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.45.1': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.45.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.45.1': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.45.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.45.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.45.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.45.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.45.1': - optional: true - - '@testing-library/dom@10.4.0': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.27.6 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - - '@testing-library/jest-dom@6.6.3': - dependencies: - '@adobe/css-tools': 4.4.3 - aria-query: 5.3.2 - chalk: 3.0.0 - css.escape: 1.5.1 - dom-accessibility-api: 0.6.3 - lodash: 4.17.21 - redent: 3.0.0 - - '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.0)': - dependencies: - '@testing-library/dom': 10.4.0 - - '@types/aria-query@5.0.4': {} - - '@types/chai@5.2.2': - dependencies: - '@types/deep-eql': 4.0.2 - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.8': {} - - '@vitest/expect@3.2.4': - dependencies: - '@types/chai': 5.2.2 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.1 - tinyrainbow: 2.0.0 - - '@vitest/mocker@3.2.4(vite@7.0.5)': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 7.0.5 - - '@vitest/pretty-format@3.2.4': - dependencies: - tinyrainbow: 2.0.0 - - '@vitest/runner@3.2.4': - dependencies: - '@vitest/utils': 3.2.4 - pathe: 2.0.3 - strip-literal: 3.0.0 - - '@vitest/snapshot@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.17 - pathe: 2.0.3 - - '@vitest/spy@3.2.4': - dependencies: - tinyspy: 4.0.3 - - '@vitest/ui@3.2.4(vitest@3.2.4)': - dependencies: - '@vitest/utils': 3.2.4 - fflate: 0.8.2 - flatted: 3.3.3 - pathe: 2.0.3 - sirv: 3.0.1 - tinyglobby: 0.2.14 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@vitest/ui@3.2.4)(jsdom@26.1.0) - - '@vitest/utils@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.2.0 - tinyrainbow: 2.0.0 - - agent-base@7.1.4: {} - - ansi-regex@5.0.1: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - aria-query@5.3.0: - dependencies: - dequal: 2.0.3 - - aria-query@5.3.2: {} - - assertion-error@2.0.1: {} - - cac@6.7.14: {} - - chai@5.2.1: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.2.0 - pathval: 2.0.1 - - chalk@3.0.0: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - check-error@2.1.1: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - css.escape@1.5.1: {} - - cssstyle@4.6.0: - dependencies: - '@asamuzakjp/css-color': 3.2.0 - rrweb-cssom: 0.8.0 - - data-urls@5.0.0: - dependencies: - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 - - debug@4.4.1: - dependencies: - ms: 2.1.3 - - decimal.js@10.6.0: {} - - deep-eql@5.0.2: {} - - dequal@2.0.3: {} - - dom-accessibility-api@0.5.16: {} - - dom-accessibility-api@0.6.3: {} - - entities@6.0.1: {} - - es-module-lexer@1.7.0: {} - - esbuild@0.25.8: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.8 - '@esbuild/android-arm': 0.25.8 - '@esbuild/android-arm64': 0.25.8 - '@esbuild/android-x64': 0.25.8 - '@esbuild/darwin-arm64': 0.25.8 - '@esbuild/darwin-x64': 0.25.8 - '@esbuild/freebsd-arm64': 0.25.8 - '@esbuild/freebsd-x64': 0.25.8 - '@esbuild/linux-arm': 0.25.8 - '@esbuild/linux-arm64': 0.25.8 - '@esbuild/linux-ia32': 0.25.8 - '@esbuild/linux-loong64': 0.25.8 - '@esbuild/linux-mips64el': 0.25.8 - '@esbuild/linux-ppc64': 0.25.8 - '@esbuild/linux-riscv64': 0.25.8 - '@esbuild/linux-s390x': 0.25.8 - '@esbuild/linux-x64': 0.25.8 - '@esbuild/netbsd-arm64': 0.25.8 - '@esbuild/netbsd-x64': 0.25.8 - '@esbuild/openbsd-arm64': 0.25.8 - '@esbuild/openbsd-x64': 0.25.8 - '@esbuild/openharmony-arm64': 0.25.8 - '@esbuild/sunos-x64': 0.25.8 - '@esbuild/win32-arm64': 0.25.8 - '@esbuild/win32-ia32': 0.25.8 - '@esbuild/win32-x64': 0.25.8 - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - expect-type@1.2.2: {} - - fdir@6.4.6(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - fflate@0.8.2: {} - - flatted@3.3.3: {} - - fsevents@2.3.3: - optional: true - - has-flag@4.0.0: {} - - html-encoding-sniffer@4.0.0: - dependencies: - whatwg-encoding: 3.1.1 - - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - indent-string@4.0.0: {} - - is-potential-custom-element-name@1.0.1: {} - - js-tokens@4.0.0: {} - - js-tokens@9.0.1: {} - - jsdom@26.1.0: - dependencies: - cssstyle: 4.6.0 - data-urls: 5.0.0 - decimal.js: 10.6.0 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.20 - parse5: 7.3.0 - rrweb-cssom: 0.8.0 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 5.1.2 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 - ws: 8.18.3 - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - lodash@4.17.21: {} - - loupe@3.2.0: {} - - lru-cache@10.4.3: {} - - lz-string@1.5.0: {} - - magic-string@0.30.17: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.4 - - min-indent@1.0.1: {} - - mrmime@2.0.1: {} - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - nwsapi@2.2.20: {} - - parse5@7.3.0: - dependencies: - entities: 6.0.1 - - pathe@2.0.3: {} - - pathval@2.0.1: {} - - picocolors@1.1.1: {} - - picomatch@4.0.3: {} - - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - pretty-format@27.5.1: - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - - punycode@2.3.1: {} - - react-is@17.0.2: {} - - redent@3.0.0: - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - - rollup@4.45.1: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.45.1 - '@rollup/rollup-android-arm64': 4.45.1 - '@rollup/rollup-darwin-arm64': 4.45.1 - '@rollup/rollup-darwin-x64': 4.45.1 - '@rollup/rollup-freebsd-arm64': 4.45.1 - '@rollup/rollup-freebsd-x64': 4.45.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.45.1 - '@rollup/rollup-linux-arm-musleabihf': 4.45.1 - '@rollup/rollup-linux-arm64-gnu': 4.45.1 - '@rollup/rollup-linux-arm64-musl': 4.45.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.45.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.45.1 - '@rollup/rollup-linux-riscv64-gnu': 4.45.1 - '@rollup/rollup-linux-riscv64-musl': 4.45.1 - '@rollup/rollup-linux-s390x-gnu': 4.45.1 - '@rollup/rollup-linux-x64-gnu': 4.45.1 - '@rollup/rollup-linux-x64-musl': 4.45.1 - '@rollup/rollup-win32-arm64-msvc': 4.45.1 - '@rollup/rollup-win32-ia32-msvc': 4.45.1 - '@rollup/rollup-win32-x64-msvc': 4.45.1 - fsevents: 2.3.3 - - rrweb-cssom@0.8.0: {} - - safer-buffer@2.1.2: {} - - saxes@6.0.0: - dependencies: - xmlchars: 2.2.0 - - siginfo@2.0.0: {} - - sirv@3.0.1: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - - source-map-js@1.2.1: {} - - stackback@0.0.2: {} - - std-env@3.9.0: {} - - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - - strip-literal@3.0.0: - dependencies: - js-tokens: 9.0.1 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - symbol-tree@3.2.4: {} - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - - tinyglobby@0.2.14: - dependencies: - fdir: 6.4.6(picomatch@4.0.3) - picomatch: 4.0.3 - - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@4.0.3: {} - - tldts-core@6.1.86: {} - - tldts@6.1.86: - dependencies: - tldts-core: 6.1.86 - - totalist@3.0.1: {} - - tough-cookie@5.1.2: - dependencies: - tldts: 6.1.86 - - tr46@5.1.1: - dependencies: - punycode: 2.3.1 - - vite-node@3.2.4: - dependencies: - cac: 6.7.14 - debug: 4.4.1 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.0.5 - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite@7.0.5: - dependencies: - esbuild: 0.25.8 - fdir: 6.4.6(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.45.1 - tinyglobby: 0.2.14 - optionalDependencies: - fsevents: 2.3.3 - - vitest@3.2.4(@vitest/ui@3.2.4)(jsdom@26.1.0): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.5) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.1 - debug: 4.4.1 - expect-type: 1.2.2 - magic-string: 0.30.17 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.0.5 - vite-node: 3.2.4 - why-is-node-running: 2.3.0 - optionalDependencies: - '@vitest/ui': 3.2.4(vitest@3.2.4) - jsdom: 26.1.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - w3c-xmlserializer@5.0.0: - dependencies: - xml-name-validator: 5.0.0 - - webidl-conversions@7.0.0: {} - - whatwg-encoding@3.1.1: - dependencies: - iconv-lite: 0.6.3 - - whatwg-mimetype@4.0.0: {} - - whatwg-url@14.2.0: - dependencies: - tr46: 5.1.1 - webidl-conversions: 7.0.0 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - ws@8.18.3: {} - - xml-name-validator@5.0.0: {} - - xmlchars@2.2.0: {} diff --git a/src/advanced/App.tsx b/src/advanced/App.tsx new file mode 100644 index 000000000..ec29213fc --- /dev/null +++ b/src/advanced/App.tsx @@ -0,0 +1,44 @@ +import React, { useEffect } from 'react' +import { ProductProvider } from './hooks/ProductContext' +import { CartProvider } from './hooks/CartContext' +import { useCart } from './hooks/useCart' +import { useSaleTimers } from './hooks/useSaleTimers' +import { Header } from './components/Header' +import { LeftColumn } from './components/LeftColumn' +import { OrderSummary } from './components/OrderSummary' +import { HelpModal } from './components/HelpModal' + +function AppContent() { + const { updateCart } = useCart() + + // 세일 타이머 활성화 + useSaleTimers() + + useEffect(() => { + // 초기화 로직 + updateCart() + }, [updateCart]) + + return ( +
+
+
+ + +
+ +
+ ) +} + +function App() { + return ( + + + + + + ) +} + +export default App \ No newline at end of file diff --git a/src/advanced/__tests__/advanced.test.js b/src/advanced/__tests__/advanced.test.ts similarity index 98% rename from src/advanced/__tests__/advanced.test.js rename to src/advanced/__tests__/advanced.test.ts index dcf803494..d2bf5b669 100644 --- a/src/advanced/__tests__/advanced.test.js +++ b/src/advanced/__tests__/advanced.test.ts @@ -4,4 +4,4 @@ describe('advanced test', () => { it('테스트코드를 자유롭게 한번 연습해보세요.', () => { expect(true).toBe(true); }); -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/src/advanced/components/CartItem.tsx b/src/advanced/components/CartItem.tsx new file mode 100644 index 000000000..b4f407e72 --- /dev/null +++ b/src/advanced/components/CartItem.tsx @@ -0,0 +1,78 @@ +import React from 'react' +import { useCart } from '../hooks/useCart' +import { formatProductPrice, formatProductName } from '../utils/formatters' +import { THRESHOLDS } from '../constants' +import { Product } from '../types' + +interface CartItemProps { + product: Product + quantity: number +} + +export function CartItem({ product, quantity }: CartItemProps) { + const { changeQuantity, removeFromCart } = useCart() + + const handleQuantityChange = (change: number) => { + changeQuantity(product.id, change) + } + + const handleRemove = () => { + removeFromCart(product.id) + } + + const price = formatProductPrice(product) + const productName = formatProductName(product) + const isBoldPrice = quantity >= THRESHOLDS.MIN_QUANTITY_FOR_DISCOUNT + + return ( +
+
+
+
+ +
+

+ {productName} +

+

PRODUCT

+

+

+ + + {quantity} + + +
+
+ +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/src/advanced/components/CartItems.tsx b/src/advanced/components/CartItems.tsx new file mode 100644 index 000000000..55dd55f95 --- /dev/null +++ b/src/advanced/components/CartItems.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import { useCart } from '../hooks/useCart' +import { CartItem } from './CartItem' + +export function CartItems() { + const { cartItems, products } = useCart() + + return ( +
+ {Object.entries(cartItems).map(([productId, quantity]) => { + const product = products.find((p) => p.id === productId) + if (!product) return null + + return ( + + ) + })} +
+ ) +} \ No newline at end of file diff --git a/src/advanced/components/Header.tsx b/src/advanced/components/Header.tsx new file mode 100644 index 000000000..786765e27 --- /dev/null +++ b/src/advanced/components/Header.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import { useCart } from '../hooks/useCart' + +export function Header() { + const { totalQuantity } = useCart() + + return ( +
+

🛒 Hanghae Online Store

+
Shopping Cart
+

+ 🛍️ {totalQuantity} items in cart +

+
+ ) +} \ No newline at end of file diff --git a/src/advanced/components/HelpModal.tsx b/src/advanced/components/HelpModal.tsx new file mode 100644 index 000000000..5f66b85e2 --- /dev/null +++ b/src/advanced/components/HelpModal.tsx @@ -0,0 +1,101 @@ +import React, { useState } from 'react' + +export function HelpModal() { + const [isOpen, setIsOpen] = useState(false) + + return ( + <> + + +
setIsOpen(false)} + > +
e.stopPropagation()} + > + + +

📖 이용 안내

+ +
+

💰 할인 정책

+
+
+

개별 상품

+

+ • 키보드 10개↑: 10%
+ • 마우스 10개↑: 15%
+ • 모니터암 10개↑: 20%
+ • 스피커 10개↑: 25% +

+
+ +
+

전체 수량

+

• 30개 이상: 25%

+
+ +
+

특별 할인

+

+ • 화요일: +10%
+ • ⚡번개세일: 20%
+ • 💝추천할인: 5% +

+
+
+
+ +
+

🎁 포인트 적립

+
+
+

기본

+

• 구매액의 0.1%

+
+ +
+

추가

+

+ • 화요일: 2배
+ • 키보드+마우스: +50p
+ • 풀세트: +100p
+ • 10개↑: +20p / 20개↑: +50p / 30개↑: +100p +

+
+
+
+ +
+

💡 TIP

+

+ • 화요일 대량구매 = MAX 혜택
+ • ⚡+💝 중복 가능
+ • 상품4 = 품절 +

+
+
+
+ + ) +} \ No newline at end of file diff --git a/src/advanced/components/LeftColumn.tsx b/src/advanced/components/LeftColumn.tsx new file mode 100644 index 000000000..0ef977727 --- /dev/null +++ b/src/advanced/components/LeftColumn.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { ProductContainer } from './ProductContainer' +import { CartItems } from './CartItems' + +export function LeftColumn() { + return ( +
+ + +
+ ) +} \ No newline at end of file diff --git a/src/advanced/components/OrderSummary.tsx b/src/advanced/components/OrderSummary.tsx new file mode 100644 index 000000000..98a9f6c5f --- /dev/null +++ b/src/advanced/components/OrderSummary.tsx @@ -0,0 +1,197 @@ +import React from 'react' +import { useCart } from '../hooks/useCart' +import { formatPrice, formatDiscountRate } from '../utils/formatters' +import { PRODUCT_IDS, THRESHOLDS } from '../constants' + +export function OrderSummary() { + const { cartItems, products, totalAmount, totalQuantity, bonusPoints } = useCart() + + const isTuesday = new Date().getDay() === 2 + + // 서브토탈 계산 + const subtotal = Object.entries(cartItems).reduce((sum, [productId, quantity]) => { + const product = products.find((p) => p.id === productId) + return sum + (product ? product.price * quantity : 0) + }, 0) + + const discountRate = subtotal > 0 ? (subtotal - totalAmount) / subtotal : 0 + const savedAmount = subtotal - totalAmount + + // 개별 할인 정보 + const getItemDiscounts = () => { + const discounts: Array<{ name: string; discount: number }> = [] + + Object.entries(cartItems).forEach(([productId, quantity]) => { + const product = products.find((p) => p.id === productId) + if (!product || quantity < THRESHOLDS.MIN_QUANTITY_FOR_DISCOUNT) return + + const discountMap = { + [PRODUCT_IDS.KEYBOARD]: 10, + [PRODUCT_IDS.MOUSE]: 15, + [PRODUCT_IDS.MONITOR_ARM]: 20, + [PRODUCT_IDS.LAPTOP_POUCH]: 5, + [PRODUCT_IDS.SPEAKER]: 25, + } + + const discount = (discountMap as any)[productId] + if (discount) { + discounts.push({ name: product.name, discount }) + } + }) + + return discounts + } + + const itemDiscounts = getItemDiscounts() + + // 포인트 상세 정보 + const getPointsDetail = () => { + const details: string[] = [] + let basePoints = Math.floor(totalAmount / 1000) + + if (basePoints > 0) { + details.push(`기본: ${basePoints}p`) + } + + if (isTuesday && basePoints > 0) { + details.push('화요일 2배') + } + + const hasKeyboard = cartItems[PRODUCT_IDS.KEYBOARD] + const hasMouse = cartItems[PRODUCT_IDS.MOUSE] + const hasMonitorArm = cartItems[PRODUCT_IDS.MONITOR_ARM] + + if (hasKeyboard && hasMouse) { + details.push('키보드+마우스 세트 +50p') + } + if (hasKeyboard && hasMouse && hasMonitorArm) { + details.push('풀세트 구매 +100p') + } + + if (totalQuantity >= 30) { + details.push('대량구매(30개+) +100p') + } else if (totalQuantity >= 20) { + details.push('대량구매(20개+) +50p') + } else if (totalQuantity >= 10) { + details.push('대량구매(10개+) +20p') + } + + return details + } + + return ( +
+

+ Order Summary +

+ +
+
+ {subtotal > 0 && ( + <> + {Object.entries(cartItems).map(([productId, quantity]) => { + const product = products.find((p) => p.id === productId) + if (!product) return null + + const itemTotal = product.price * quantity + return ( +
+ {product.name} x {quantity} + {formatPrice(itemTotal)} +
+ ) + })} + +
+ +
+ Subtotal + {formatPrice(subtotal)} +
+ + {totalQuantity >= THRESHOLDS.MIN_QUANTITY_FOR_BULK ? ( +
+ 🎉 대량구매 할인 (30개 이상) + -25% +
+ ) : ( + itemDiscounts.map((item) => ( +
+ {item.name} (10개↑) + -{item.discount}% +
+ )) + )} + + {isTuesday && totalQuantity > 0 && ( +
+ 🌟 화요일 추가 할인 + -10% +
+ )} + +
+ Shipping + Free +
+ + )} +
+ +
+ {discountRate > 0 && totalAmount > 0 && ( +
+
+ 총 할인율 + + {formatDiscountRate(discountRate)} + +
+
+ {formatPrice(savedAmount)} 할인되었습니다 +
+
+ )} + +
+
+ Total +
+ {formatPrice(totalAmount)} +
+
+ + {Object.keys(cartItems).length > 0 && ( +
+
적립 포인트: {bonusPoints}p
+
+ {getPointsDetail().join(', ')} +
+
+ )} +
+ + {isTuesday && ( +
+
+ 🎉 + + Tuesday Special 10% Applied + +
+
+ )} +
+
+ + + +

+ Free shipping on all orders.
+ Earn loyalty points with purchase. +

+
+ ) +} \ No newline at end of file diff --git a/src/advanced/components/ProductContainer.tsx b/src/advanced/components/ProductContainer.tsx new file mode 100644 index 000000000..cf4d36c4d --- /dev/null +++ b/src/advanced/components/ProductContainer.tsx @@ -0,0 +1,73 @@ +import React, { useState } from 'react' +import { useCart } from '../hooks/useCart' +import { formatProductOption } from '../utils/formatters' +import { THRESHOLDS } from '../constants' + +export function ProductContainer() { + const { products, addToCart } = useCart() + const [selectedProductId, setSelectedProductId] = useState('') + + const totalStock = products.reduce((sum, product) => sum + product.stock, 0) + + const handleAddToCart = () => { + if (selectedProductId) { + addToCart(selectedProductId) + } + } + + const getStockMessage = () => { + let message = '' + products.forEach((product) => { + if (product.stock < THRESHOLDS.LOW_STOCK) { + if (product.stock > 0) { + message += `${product.name}: 재고 부족 (${product.stock}개 남음)\n` + } else { + message += `${product.name}: 품절\n` + } + } + }) + return message + } + + const stockMessage = getStockMessage() + + return ( +
+ + + + + {stockMessage && ( +
+ {stockMessage} +
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/advanced/constants/index.ts b/src/advanced/constants/index.ts new file mode 100644 index 000000000..abd557617 --- /dev/null +++ b/src/advanced/constants/index.ts @@ -0,0 +1,45 @@ +// constants/index.ts +export const PRODUCT_IDS = { + KEYBOARD: 'p1', + MOUSE: 'p2', + MONITOR_ARM: 'p3', + LAPTOP_POUCH: 'p4', + SPEAKER: 'p5', +} as const; + +export const DISCOUNT_RATES = { + KEYBOARD: 0.1, + MOUSE: 0.15, + MONITOR_ARM: 0.2, + LAPTOP_POUCH: 0.05, + SPEAKER: 0.25, + BULK: 0.25, + TUESDAY: 0.1, + LIGHTNING: 0.2, + RECOMMEND: 0.05, +} as const; + +export const THRESHOLDS = { + MIN_QUANTITY_FOR_DISCOUNT: 10, + MIN_QUANTITY_FOR_BULK: 30, + LOW_STOCK: 5, + TOTAL_STOCK_WARNING: 50, +} as const; + +export const POINTS = { + RATE: 0.001, + BONUS: { + SET: 50, + FULL_SET: 100, + BULK_10: 20, + BULK_20: 50, + BULK_30: 100, + }, +} as const; + +export const TIMERS = { + LIGHTNING_SALE_INTERVAL: 30000, + RECOMMEND_SALE_INTERVAL: 60000, + LIGHTNING_SALE_MAX_DELAY: 10000, + RECOMMEND_SALE_MAX_DELAY: 20000, +} as const; \ No newline at end of file diff --git a/src/advanced/data/products.ts b/src/advanced/data/products.ts new file mode 100644 index 000000000..be79da31b --- /dev/null +++ b/src/advanced/data/products.ts @@ -0,0 +1,53 @@ +// data/products.ts +import { PRODUCT_IDS } from '../constants/index.js'; +import type { Product } from '../types/index.js'; + +export function initializeProducts(): Product[] { + return [ + { + id: PRODUCT_IDS.KEYBOARD, + name: '버그 없애는 키보드', + price: 10000, + originalPrice: 10000, + stock: 50, + onSale: false, + recommendSale: false, + }, + { + id: PRODUCT_IDS.MOUSE, + name: '생산성 폭발 마우스', + price: 20000, + originalPrice: 20000, + stock: 30, + onSale: false, + recommendSale: false, + }, + { + id: PRODUCT_IDS.MONITOR_ARM, + name: '거북목 탈출 모니터암', + price: 30000, + originalPrice: 30000, + stock: 20, + onSale: false, + recommendSale: false, + }, + { + id: PRODUCT_IDS.LAPTOP_POUCH, + name: '에러 방지 노트북 파우치', + price: 15000, + originalPrice: 15000, + stock: 0, + onSale: false, + recommendSale: false, + }, + { + id: PRODUCT_IDS.SPEAKER, + name: '코딩할 때 듣는 Lo-Fi 스피커', + price: 25000, + originalPrice: 25000, + stock: 10, + onSale: false, + recommendSale: false, + }, + ]; +} \ No newline at end of file diff --git a/src/advanced/hooks/CartContext.tsx b/src/advanced/hooks/CartContext.tsx new file mode 100644 index 000000000..14d79ab93 --- /dev/null +++ b/src/advanced/hooks/CartContext.tsx @@ -0,0 +1,43 @@ +import React, { createContext, useContext, ReactNode, useState } from 'react' +import { CartContextType, CartState } from '../types' + +const CartContext = createContext(undefined) + +export function CartProvider({ children }: { children: ReactNode }) { + const [cartItems, setCartItems] = useState>({}) + const [totalAmount, setTotalAmount] = useState(0) + const [totalQuantity, setTotalQuantity] = useState(0) + const [bonusPoints, setBonusPoints] = useState(0) + const [lastSelectedProductId, setLastSelectedProductId] = useState(null) + + const state: CartState = { + cartItems, + totalAmount, + totalQuantity, + bonusPoints, + lastSelectedProductId, + } + + const value: CartContextType = { + state, + setCartItems, + setTotalAmount, + setTotalQuantity, + setBonusPoints, + setLastSelectedProductId, + } + + return ( + + {children} + + ) +} + +export function useCartContext() { + const context = useContext(CartContext) + if (!context) { + throw new Error('useCartContext must be used within CartProvider') + } + return context +} \ No newline at end of file diff --git a/src/advanced/hooks/ProductContext.tsx b/src/advanced/hooks/ProductContext.tsx new file mode 100644 index 000000000..649fb941d --- /dev/null +++ b/src/advanced/hooks/ProductContext.tsx @@ -0,0 +1,45 @@ +import React, { createContext, useContext, ReactNode, useState, useEffect } from 'react' +import { Product, ProductContextType, ProductState } from '../types' +import { initializeProducts } from '../data/products' + +const ProductContext = createContext(undefined) + +export function ProductProvider({ children }: { children: ReactNode }) { + const [products, setProducts] = useState([]) + + // 초기화 + useEffect(() => { + setProducts(initializeProducts()) + }, []) + + // 상품 업데이트 (세일 등) + const updateProduct = (productId: string, updates: Partial) => { + setProducts(prev => prev.map(p => + p.id === productId ? { ...p, ...updates } : p + )) + } + + const state: ProductState = { + products, + } + + const value: ProductContextType = { + state, + setProducts, + updateProduct, + } + + return ( + + {children} + + ) +} + +export function useProductContext() { + const context = useContext(ProductContext) + if (!context) { + throw new Error('useProductContext must be used within ProductProvider') + } + return context +} \ No newline at end of file diff --git a/src/advanced/hooks/useCart.ts b/src/advanced/hooks/useCart.ts new file mode 100644 index 000000000..f9afd1479 --- /dev/null +++ b/src/advanced/hooks/useCart.ts @@ -0,0 +1,116 @@ +import { useCallback, useEffect } from 'react' +import { useProductContext } from './ProductContext' +import { useCartContext } from './CartContext' +import { calculateFinalAmount } from '../services/discount' +import { calculateBonusPoints } from '../services/points' +import { canAddToCart, canChangeQuantity, updateProductStock, updateCartQuantity } from '../services/cart' + +export function useCart() { + // 두 개의 분리된 Context 사용 + const productContext = useProductContext() + const cartContext = useCartContext() + + // 상태들 + const { products } = productContext.state + const { cartItems, totalAmount, totalQuantity, bonusPoints, lastSelectedProductId } = cartContext.state + + // 장바구니 업데이트 로직 + const updateCart = useCallback(() => { + const result = calculateFinalAmount(cartItems, products) + const newBonusPoints = calculateBonusPoints(result.totalAmount, result.totalQuantity, cartItems) + + cartContext.setTotalAmount(result.totalAmount) + cartContext.setTotalQuantity(result.totalQuantity) + cartContext.setBonusPoints(newBonusPoints) + }, [cartItems, products, cartContext]) + + // 자동 업데이트 + useEffect(() => { + updateCart() + }, [updateCart]) + + // 장바구니에 추가 + const addToCart = useCallback((productId: string) => { + const product = products.find(p => p.id === productId) + if (!product || !canAddToCart(product)) { + alert('재고가 부족합니다.') + return + } + + // 장바구니 업데이트 (CartContext) + const currentQuantity = cartItems[productId] || 0 + const newCartItems = updateCartQuantity(cartItems, productId, currentQuantity + 1) + cartContext.setCartItems(newCartItems) + + // 재고 업데이트 (ProductContext) + const newProducts = updateProductStock(products, productId, -1) + productContext.setProducts(newProducts) + + cartContext.setLastSelectedProductId(productId) + }, [products, cartItems, productContext, cartContext]) + + // 수량 변경 + const changeQuantity = useCallback((productId: string, change: number) => { + const product = products.find(p => p.id === productId) + if (!product) return + + const currentQuantity = cartItems[productId] || 0 + const newQuantity = currentQuantity + change + + if (newQuantity <= 0) { + // 제거 + const newCartItems = updateCartQuantity(cartItems, productId, 0) + cartContext.setCartItems(newCartItems) + + const newProducts = updateProductStock(products, productId, currentQuantity) + productContext.setProducts(newProducts) + } else { + if (!canChangeQuantity(product, change)) { + alert('재고가 부족합니다.') + return + } + + // 수량 변경 + const newCartItems = updateCartQuantity(cartItems, productId, newQuantity) + cartContext.setCartItems(newCartItems) + + const newProducts = updateProductStock(products, productId, -change) + productContext.setProducts(newProducts) + } + }, [products, cartItems, productContext, cartContext]) + + // 제거 + const removeFromCart = useCallback((productId: string) => { + const quantity = cartItems[productId] || 0 + + // 장바구니에서 제거 + const newCartItems = updateCartQuantity(cartItems, productId, 0) + cartContext.setCartItems(newCartItems) + + // 재고 복구 + const newProducts = updateProductStock(products, productId, quantity) + productContext.setProducts(newProducts) + }, [cartItems, products, productContext, cartContext]) + + // 상품 업데이트 (세일 등) + const updateProduct = useCallback((productId: string, updates: Partial) => { + productContext.updateProduct(productId, updates) + }, [productContext]) + + return { + // 상태 + products, + cartItems, + totalAmount, + totalQuantity, + bonusPoints, + lastSelectedProductId, + + // 액션들 + addToCart, + changeQuantity, + removeFromCart, + updateProduct, + updateCart, + } +} \ No newline at end of file diff --git a/src/advanced/hooks/useSaleTimers.ts b/src/advanced/hooks/useSaleTimers.ts new file mode 100644 index 000000000..5950abc9c --- /dev/null +++ b/src/advanced/hooks/useSaleTimers.ts @@ -0,0 +1,79 @@ +import { useEffect, useCallback } from 'react' +import { useCart } from './useCart' +import { DISCOUNT_RATES, TIMERS } from '../constants' + +export function useSaleTimers() { + const { products, cartItems, lastSelectedProductId, updateProduct } = useCart() + + // 번개세일 로직 + const startLightningSale = useCallback(() => { + const availableProducts = products.filter((p) => p.stock > 0 && !p.onSale) + + if (availableProducts.length === 0) return + + const randomProduct = availableProducts[Math.floor(Math.random() * availableProducts.length)] + const newPrice = Math.round(randomProduct.originalPrice * (1 - DISCOUNT_RATES.LIGHTNING)) + + updateProduct(randomProduct.id, { + price: newPrice, + onSale: true, + }) + + alert(`⚡번개세일! ${randomProduct.name}이(가) 20% 할인 중입니다!`) + }, [products, updateProduct]) + + // 추천세일 로직 + const startRecommendSale = useCallback(() => { + if (Object.keys(cartItems).length === 0 || !lastSelectedProductId) return + + const recommendableProducts = products.filter( + (p) => p.id !== lastSelectedProductId && p.stock > 0 && !p.recommendSale + ) + + if (recommendableProducts.length === 0) return + + const recommendProduct = recommendableProducts[0] + const newPrice = Math.round(recommendProduct.price * (1 - DISCOUNT_RATES.RECOMMEND)) + + alert(`💝 ${recommendProduct.name}은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!`) + + updateProduct(recommendProduct.id, { + price: newPrice, + recommendSale: true, + }) + }, [products, cartItems, lastSelectedProductId, updateProduct]) + + // 번개세일 타이머 설정 + useEffect(() => { + const delay = Math.random() * TIMERS.LIGHTNING_SALE_MAX_DELAY + + const timeoutId = setTimeout(() => { + const intervalId = setInterval(() => { + startLightningSale() + }, TIMERS.LIGHTNING_SALE_INTERVAL) + + // cleanup을 위해 interval ID 저장 + return () => clearInterval(intervalId) + }, delay) + + return () => clearTimeout(timeoutId) + }, [startLightningSale]) + + // 추천세일 타이머 설정 + useEffect(() => { + const delay = Math.random() * TIMERS.RECOMMEND_SALE_MAX_DELAY + + const timeoutId = setTimeout(() => { + const intervalId = setInterval(() => { + startRecommendSale() + }, TIMERS.RECOMMEND_SALE_INTERVAL) + + // cleanup을 위해 interval ID 저장 + return () => clearInterval(intervalId) + }, delay) + + return () => clearTimeout(timeoutId) + }, [startRecommendSale]) + + // 이 훅은 side effect만 수행하므로 반환값 없음 +} \ No newline at end of file diff --git a/src/advanced/index.tsx b/src/advanced/index.tsx new file mode 100644 index 000000000..eca7eb089 --- /dev/null +++ b/src/advanced/index.tsx @@ -0,0 +1,8 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +const root = ReactDOM.createRoot( + document.getElementById('app') as HTMLElement +) +root.render() \ No newline at end of file diff --git a/src/advanced/main.advanced.js b/src/advanced/main.advanced.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/advanced/services/cart.ts b/src/advanced/services/cart.ts new file mode 100644 index 000000000..0415f29cb --- /dev/null +++ b/src/advanced/services/cart.ts @@ -0,0 +1,43 @@ +// services/cart.ts +import { Product } from '../types/index.js'; + +// React용 장바구니 로직들 (순수 함수로 만듦) +export function canAddToCart(product: Product): boolean { + return product && product.stock > 0; +} + +export function canChangeQuantity(product: Product, change: number): boolean { + if (change > 0) { + return product.stock > 0; + } + return true; // 감소는 항상 가능 +} + +export function updateProductStock( + products: Product[], + productId: string, + stockChange: number +): Product[] { + return products.map(p => + p.id === productId + ? { ...p, stock: p.stock + stockChange } + : p + ); +} + +export function updateCartQuantity( + cartItems: Record, + productId: string, + newQuantity: number +): Record { + if (newQuantity <= 0) { + const newItems = { ...cartItems }; + delete newItems[productId]; + return newItems; + } + + return { + ...cartItems, + [productId]: newQuantity + }; +} \ No newline at end of file diff --git a/src/advanced/services/discount.ts b/src/advanced/services/discount.ts new file mode 100644 index 000000000..6e8b4924b --- /dev/null +++ b/src/advanced/services/discount.ts @@ -0,0 +1,88 @@ +// services/discount.ts +import { PRODUCT_IDS, DISCOUNT_RATES, THRESHOLDS } from '../constants/index.js'; +import type { ItemDiscount, Product } from '../types/index.js'; + +export function calculateItemDiscount(productId: string, quantity: number): number { + if (quantity < THRESHOLDS.MIN_QUANTITY_FOR_DISCOUNT) return 0; + + const discountMap: Record = { + [PRODUCT_IDS.KEYBOARD]: DISCOUNT_RATES.KEYBOARD, + [PRODUCT_IDS.MOUSE]: DISCOUNT_RATES.MOUSE, + [PRODUCT_IDS.MONITOR_ARM]: DISCOUNT_RATES.MONITOR_ARM, + [PRODUCT_IDS.LAPTOP_POUCH]: DISCOUNT_RATES.LAPTOP_POUCH, + [PRODUCT_IDS.SPEAKER]: DISCOUNT_RATES.SPEAKER, + }; + + return discountMap[productId] || 0; +} + +export function calculateTotalWithDiscounts(cartItems: Record, products: Product[]) { + let subtotal = 0; + let totalWithItemDiscounts = 0; + let itemDiscounts: ItemDiscount[] = []; + let totalQuantity = 0; + + Object.entries(cartItems).forEach(([productId, quantity]) => { + const product = products.find(p => p.id === productId); + if (!product) return; + + totalQuantity += quantity; + const itemTotal = product.price * quantity; + subtotal += itemTotal; + + const discountRate = calculateItemDiscount(productId, quantity); + if (discountRate > 0) { + itemDiscounts.push({ name: product.name, discount: discountRate * 100 }); + totalWithItemDiscounts += itemTotal * (1 - discountRate); + } else { + totalWithItemDiscounts += itemTotal; + } + }); + + return { subtotal, totalWithItemDiscounts, itemDiscounts, totalQuantity }; +} + +export function applyBulkDiscount(amount: number, totalQuantity: number): number { + if (totalQuantity >= THRESHOLDS.MIN_QUANTITY_FOR_BULK) { + return amount * (1 - DISCOUNT_RATES.BULK); + } + return amount; +} + +export function applyTuesdayDiscount(amount: number): number { + const isTuesday = new Date().getDay() === 2; + if (isTuesday && amount > 0) { + return amount * (1 - DISCOUNT_RATES.TUESDAY); + } + return amount; +} + +export function calculateFinalAmount(cartItems: Record, products: Product[]) { + let totalAmount = 0; + let totalQuantity = 0; + let subtotal = 0; + + // 각 아이템별 계산 + Object.entries(cartItems).forEach(([productId, quantity]) => { + const product = products.find(p => p.id === productId); + if (!product) return; + + totalQuantity += quantity; + const itemTotal = product.price * quantity; + subtotal += itemTotal; + + // 개별 할인 적용 + const discountRate = calculateItemDiscount(productId, quantity); + totalAmount += itemTotal * (1 - discountRate); + }); + + // 대량 구매 할인 + if (totalQuantity >= THRESHOLDS.MIN_QUANTITY_FOR_BULK) { + totalAmount = subtotal * (1 - DISCOUNT_RATES.BULK); + } + + // 화요일 할인 + totalAmount = applyTuesdayDiscount(totalAmount); + + return { totalAmount, totalQuantity, subtotal }; +} \ No newline at end of file diff --git a/src/advanced/services/points.ts b/src/advanced/services/points.ts new file mode 100644 index 000000000..4efb77cab --- /dev/null +++ b/src/advanced/services/points.ts @@ -0,0 +1,47 @@ +// services/points.ts +import { PRODUCT_IDS, POINTS } from '../constants/index.js'; + +// React용 포인트 계산 함수 (파라미터로 값들을 받음) +export function calculateBonusPoints( + totalAmount: number, + totalQuantity: number, + cartItems: Record +): number { + if (Object.keys(cartItems).length === 0) { + return 0; + } + + // 기본 포인트는 총액을 1000으로 나눈 후 소수점 버림 + let basePoints = Math.floor(totalAmount / 1000); + let finalPoints = basePoints; + + // 화요일 2배 적용 + const isTuesday = new Date().getDay() === 2; + if (isTuesday && basePoints > 0) { + finalPoints = basePoints * 2; + } + + // 세트 보너스 + const hasKeyboard = cartItems[PRODUCT_IDS.KEYBOARD]; + const hasMouse = cartItems[PRODUCT_IDS.MOUSE]; + const hasMonitorArm = cartItems[PRODUCT_IDS.MONITOR_ARM]; + + if (hasKeyboard && hasMouse) { + finalPoints += POINTS.BONUS.SET; + } + + if (hasKeyboard && hasMouse && hasMonitorArm) { + finalPoints += POINTS.BONUS.FULL_SET; + } + + // 수량 보너스 + if (totalQuantity >= 30) { + finalPoints += POINTS.BONUS.BULK_30; + } else if (totalQuantity >= 20) { + finalPoints += POINTS.BONUS.BULK_20; + } else if (totalQuantity >= 10) { + finalPoints += POINTS.BONUS.BULK_10; + } + + return finalPoints; +} \ No newline at end of file diff --git a/src/advanced/types/index.ts b/src/advanced/types/index.ts new file mode 100644 index 000000000..cf246d216 --- /dev/null +++ b/src/advanced/types/index.ts @@ -0,0 +1,55 @@ +// types/index.ts +export interface Product { + id: string; + name: string; + price: number; + originalPrice: number; + stock: number; + onSale: boolean; + recommendSale: boolean; +} + +export interface CartItems { + [productId: string]: number; +} + +export interface ItemDiscount { + name: string; + discount: number; +} + +export interface ProductOptionFormat { + text: string; + className: string; +} + +// 장바구니 상태 타입 +export interface CartState { + cartItems: Record + totalAmount: number + totalQuantity: number + bonusPoints: number + lastSelectedProductId: string | null +} + +// Context 타입 +export interface CartContextType { + state: CartState + setCartItems: (cartItems: Record) => void + setTotalAmount: (amount: number) => void + setTotalQuantity: (quantity: number) => void + setBonusPoints: (points: number) => void + setLastSelectedProductId: (id: string | null) => void +} + +// 상품 상태 타입 +export interface ProductState { + products: Product[] +} + +// Context 타입 +export interface ProductContextType { + state: ProductState + setProducts: (products: Product[]) => void + updateProduct: (productId: string, updates: Partial) => void +} diff --git a/src/advanced/utils/formatters.ts b/src/advanced/utils/formatters.ts new file mode 100644 index 000000000..7a4b8d460 --- /dev/null +++ b/src/advanced/utils/formatters.ts @@ -0,0 +1,66 @@ +// utils/formatters.ts +import type { Product, ProductOptionFormat } from '../types/index.js'; + +export function formatPrice(price: number): string { + return `₩${Math.round(price).toLocaleString()}`; +} + +export function formatDiscountRate(rate: number): string { + return `${(rate * 100).toFixed(1)}%`; +} + +function determinePriceColorClass(onSale: boolean, recommendSale: boolean): string { + if (onSale && recommendSale) return 'text-purple-600'; + if (onSale) return 'text-red-500'; + if (recommendSale) return 'text-blue-500'; + return ''; +} + +export function formatProductPrice(product: Product): string { + if (!product.onSale && !product.recommendSale) { + return formatPrice(product.price); + } + + const colorClass = determinePriceColorClass( + product.onSale, + product.recommendSale, + ); + const originalHtml = `${formatPrice(product.originalPrice)}`; + const salePriceHtml = `${formatPrice(product.price)}`; + + return `${originalHtml} ${salePriceHtml}`; +} + +export function formatProductName(product: Product): string { + let saleIcon = ''; + + if (product.onSale && product.recommendSale) { + saleIcon = '⚡💝'; + } else if (product.onSale) { + saleIcon = '⚡'; + } else if (product.recommendSale) { + saleIcon = '💝'; + } + return saleIcon + product.name; +} + +export function formatProductOption(product: Product): ProductOptionFormat { + let text = `${product.name} - ${product.price}원`; + let className = ''; + + if (product.stock === 0) { + text += ' (품절)'; + className = 'text-gray-400'; + } else if (product.onSale && product.recommendSale) { + text = `⚡💝${product.name} - ${product.originalPrice}원 → ${product.price}원 (25% SUPER SALE!)`; + className = 'text-purple-600 font-bold'; + } else if (product.onSale) { + text = `⚡${product.name} - ${product.originalPrice}원 → ${product.price}원 (20% SALE!)`; + className = 'text-red-500 font-bold'; + } else if (product.recommendSale) { + text = `💝${product.name} - ${product.originalPrice}원 → ${product.price}원 (5% 추천할인!)`; + className = 'text-blue-500 font-bold'; + } + + return { text, className }; +} \ No newline at end of file diff --git a/src/basic/__tests__/basic.test.js b/src/basic/__tests__/basic.test.js index e5756b96e..b6ba8a758 100644 --- a/src/basic/__tests__/basic.test.js +++ b/src/basic/__tests__/basic.test.js @@ -1,674 +1,741 @@ -import { beforeEach, afterEach, describe, expect, it, vi } from "vitest"; -import userEvent from "@testing-library/user-event"; +import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest' +import userEvent from '@testing-library/user-event' describe('basic 테스트', () => { // 공통 헬퍼 함수 const addItemsToCart = (sel, addBtn, productId, count) => { - sel.value = productId; + sel.value = productId for (let i = 0; i < count; i++) { - addBtn.click(); + addBtn.click() } - }; + } const expectProductInfo = (option, product) => { - expect(option.value).toBe(product.id); - expect(option.textContent).toContain(product.name); - expect(option.textContent).toContain(product.price); + expect(option.value).toBe(product.id) + expect(option.textContent).toContain(product.name) + expect(option.textContent).toContain(product.price) if (product.stock === 0) { - expect(option.disabled).toBe(true); - expect(option.textContent).toContain('품절'); + expect(option.disabled).toBe(true) + expect(option.textContent).toContain('품절') } - }; + } const getCartItemQuantity = (cartDisp, productId) => { - const item = cartDisp.querySelector(`#${productId}`); - if (!item) return 0; - const qtyElement = item.querySelector('.quantity-number'); - return qtyElement ? parseInt(qtyElement.textContent) : 0; - }; + const item = cartDisp.querySelector(`#${productId}`) + if (!item) return 0 + const qtyElement = item.querySelector('.quantity-number') + return qtyElement ? parseInt(qtyElement.textContent) : 0 + } describe.each([ - { type: 'origin', loadFile: () => import('../../main.original.js'), }, - { type: 'basic', loadFile: () => import('../main.basic.js'), }, + { type: 'origin', loadFile: () => import('../../main.original.js') }, + { type: 'basic', loadFile: () => import('../main.basic.js') }, ])('$type 장바구니 상세 기능 테스트', ({ loadFile }) => { - let sel, addBtn, cartDisp, sum, stockInfo, itemCount, loyaltyPoints, discountInfo; + let sel, + addBtn, + cartDisp, + sum, + stockInfo, + itemCount, + loyaltyPoints, + discountInfo beforeEach(async () => { - vi.useRealTimers(); - vi.spyOn(window, 'alert').mockImplementation(() => {}); + vi.useRealTimers() + vi.spyOn(window, 'alert').mockImplementation(() => {}) // 전체 DOM 재초기화 - document.body.innerHTML = '
'; - + document.body.innerHTML = '
' + // 모듈 캐시 클리어 및 재로드 - vi.resetModules(); - await loadFile(); + vi.resetModules() + await loadFile() // DOM 요소 참조 - sel = document.getElementById('product-select'); - addBtn = document.getElementById('add-to-cart'); - cartDisp = document.getElementById('cart-items'); - sum = document.getElementById('cart-total'); - stockInfo = document.getElementById('stock-status'); - itemCount = document.getElementById('item-count'); - loyaltyPoints = document.getElementById('loyalty-points'); - discountInfo = document.getElementById('discount-info'); - }); + sel = document.getElementById('product-select') + addBtn = document.getElementById('add-to-cart') + cartDisp = document.getElementById('cart-items') + sum = document.getElementById('cart-total') + stockInfo = document.getElementById('stock-status') + itemCount = document.getElementById('item-count') + loyaltyPoints = document.getElementById('loyalty-points') + discountInfo = document.getElementById('discount-info') + }) afterEach(() => { - vi.restoreAllMocks(); - }); + vi.restoreAllMocks() + }) // 2. 상품 정보 테스트 describe('2. 상품 정보', () => { describe('2.1 상품 목록', () => { it('5개 상품이 올바른 정보로 표시되어야 함', () => { const expectedProducts = [ - { id: 'p1', name: '버그 없애는 키보드', price: '10000원', stock: 50, discount: 10 }, - { id: 'p2', name: '생산성 폭발 마우스', price: '20000원', stock: 30, discount: 15 }, - { id: 'p3', name: '거북목 탈출 모니터암', price: '30000원', stock: 20, discount: 20 }, - { id: 'p4', name: '에러 방지 노트북 파우치', price: '15000원', stock: 0, discount: 5 }, - { id: 'p5', name: '코딩할 때 듣는 Lo-Fi 스피커', price: '25000원', stock: 10, discount: 25 } - ]; - - expect(sel.options.length).toBe(5); + { + id: 'p1', + name: '버그 없애는 키보드', + price: '10000원', + stock: 50, + discount: 10, + }, + { + id: 'p2', + name: '생산성 폭발 마우스', + price: '20000원', + stock: 30, + discount: 15, + }, + { + id: 'p3', + name: '거북목 탈출 모니터암', + price: '30000원', + stock: 20, + discount: 20, + }, + { + id: 'p4', + name: '에러 방지 노트북 파우치', + price: '15000원', + stock: 0, + discount: 5, + }, + { + id: 'p5', + name: '코딩할 때 듣는 Lo-Fi 스피커', + price: '25000원', + stock: 10, + discount: 25, + }, + ] + + expect(sel.options.length).toBe(5) expectedProducts.forEach((product, index) => { - expectProductInfo(sel.options[index], product); - }); - }); - }); + expectProductInfo(sel.options[index], product) + }) + }) + }) describe('2.2 재고 관리', () => { it('재고가 5개 미만인 상품은 "재고 부족" 표시', async () => { // 상품5를 6개 구매하여 재고를 4개로 만듦 - addItemsToCart(sel, addBtn, 'p5', 6); + addItemsToCart(sel, addBtn, 'p5', 6) - expect(stockInfo.textContent).toContain('코딩할 때 듣는 Lo-Fi 스피커'); - expect(stockInfo.textContent).toContain('재고 부족'); - expect(stockInfo.textContent).toContain('4개 남음'); - }); + expect(stockInfo.textContent).toContain('코딩할 때 듣는 Lo-Fi 스피커') + expect(stockInfo.textContent).toContain('재고 부족') + expect(stockInfo.textContent).toContain('4개 남음') + }) it('재고가 0개인 상품은 "품절" 표시 및 선택 불가', () => { - const p4Option = sel.querySelector('option[value="p4"]'); - expect(p4Option.disabled).toBe(true); - expect(p4Option.textContent).toContain('품절'); - }); - }); - }); + const p4Option = sel.querySelector('option[value="p4"]') + expect(p4Option.disabled).toBe(true) + expect(p4Option.textContent).toContain('품절') + }) + }) + }) // 3. 할인 정책 테스트 describe('3. 할인 정책', () => { describe('3.1 개별 상품 할인', () => { it('상품1: 10개 이상 구매 시 10% 할인', () => { - addItemsToCart(sel, addBtn, 'p1', 10); + addItemsToCart(sel, addBtn, 'p1', 10) // 100,000원 -> 90,000원 - expect(sum.textContent).toContain('₩90,000'); - expect(discountInfo.textContent).toContain('10.0%'); - }); + expect(sum.textContent).toContain('₩90,000') + expect(discountInfo.textContent).toContain('10.0%') + }) it('상품2: 10개 이상 구매 시 15% 할인', () => { - addItemsToCart(sel, addBtn, 'p2', 10); + addItemsToCart(sel, addBtn, 'p2', 10) // 200,000원 -> 170,000원 - expect(sum.textContent).toContain('₩170,000'); - expect(discountInfo.textContent).toContain('15.0%'); - }); + expect(sum.textContent).toContain('₩170,000') + expect(discountInfo.textContent).toContain('15.0%') + }) it('상품3: 10개 이상 구매 시 20% 할인', () => { - addItemsToCart(sel, addBtn, 'p3', 10); + addItemsToCart(sel, addBtn, 'p3', 10) // 300,000원 -> 240,000원 - expect(sum.textContent).toContain('₩240,000'); - expect(discountInfo.textContent).toContain('20.0%'); - }); + expect(sum.textContent).toContain('₩240,000') + expect(discountInfo.textContent).toContain('20.0%') + }) it('상품5: 10개 이상 구매 시 25% 할인', () => { - addItemsToCart(sel, addBtn, 'p5', 10); + addItemsToCart(sel, addBtn, 'p5', 10) // 250,000원 -> 187,500원 - expect(sum.textContent).toContain('₩187,500'); - expect(discountInfo.textContent).toContain('25.0%'); - }); - }); + expect(sum.textContent).toContain('₩187,500') + expect(discountInfo.textContent).toContain('25.0%') + }) + }) describe('3.2 전체 수량 할인', () => { it('전체 30개 이상 구매 시 25% 할인 (개별 할인 무시)', () => { // 상품1 10개, 상품2 10개, 상품3 10개 = 총 30개 - addItemsToCart(sel, addBtn, 'p1', 10); - addItemsToCart(sel, addBtn, 'p2', 10); - addItemsToCart(sel, addBtn, 'p3', 10); + addItemsToCart(sel, addBtn, 'p1', 10) + addItemsToCart(sel, addBtn, 'p2', 10) + addItemsToCart(sel, addBtn, 'p3', 10) // 600,000원 -> 450,000원 (25% 할인) - expect(sum.textContent).toContain('₩450,000'); - expect(discountInfo.textContent).toContain('25.0%'); - }); - }); + expect(sum.textContent).toContain('₩450,000') + expect(discountInfo.textContent).toContain('25.0%') + }) + }) describe('3.3 특별 할인', () => { describe('3.3.1 화요일 할인', () => { it('화요일에 10% 추가 할인 적용', () => { - const tuesday = new Date('2024-10-15'); // 화요일 - vi.useFakeTimers(); - vi.setSystemTime(tuesday); + const tuesday = new Date('2024-10-15') // 화요일 + vi.useFakeTimers() + vi.setSystemTime(tuesday) - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() // 10,000원 -> 9,000원 (10% 할인) - expect(sum.textContent).toContain('₩9,000'); - expect(discountInfo.textContent).toContain('10.0%'); + expect(sum.textContent).toContain('₩9,000') + expect(discountInfo.textContent).toContain('10.0%') // 화요일 특별 할인 배너 표시 - const tuesdayBanner = document.getElementById('tuesday-special'); - expect(tuesdayBanner.classList.contains('hidden')).toBe(false); + const tuesdayBanner = document.getElementById('tuesday-special') + expect(tuesdayBanner.classList.contains('hidden')).toBe(false) - vi.useRealTimers(); - }); + vi.useRealTimers() + }) it('화요일 할인은 다른 할인과 중복 적용', () => { - const tuesday = new Date('2024-10-15'); - vi.useFakeTimers(); - vi.setSystemTime(tuesday); + const tuesday = new Date('2024-10-15') + vi.useFakeTimers() + vi.setSystemTime(tuesday) - addItemsToCart(sel, addBtn, 'p1', 10); + addItemsToCart(sel, addBtn, 'p1', 10) // 100,000원 -> 90,000원 (개별 10%) -> 81,000원 (화요일 10% 추가) - expect(sum.textContent).toContain('₩81,000'); - expect(discountInfo.textContent).toContain('19.0%'); // 총 19% 할인 + expect(sum.textContent).toContain('₩81,000') + expect(discountInfo.textContent).toContain('19.0%') // 총 19% 할인 - vi.useRealTimers(); - }); - }); + vi.useRealTimers() + }) + }) describe('3.3.2 번개세일', () => { it.skip('번개세일 알림 표시 및 20% 할인 적용', async () => { // 원본 코드의 타이머 구현 문제로 인해 스킵 - vi.useFakeTimers(); - await vi.advanceTimersByTimeAsync(40000); - vi.useRealTimers(); - }); + vi.useFakeTimers() + await vi.advanceTimersByTimeAsync(40000) + vi.useRealTimers() + }) it.skip('번개세일 상품은 드롭다운에 ⚡ 아이콘 표시', async () => { // 원본 코드의 타이머 구현 문제로 인해 스킵 - vi.useFakeTimers(); - await vi.advanceTimersByTimeAsync(40000); - vi.useRealTimers(); - }); - }); + vi.useFakeTimers() + await vi.advanceTimersByTimeAsync(40000) + vi.useRealTimers() + }) + }) describe('3.3.3 추천할인', () => { it.skip('마지막 선택한 상품과 다른 상품 추천 및 5% 할인', async () => { // 원본 코드의 타이머 구현 문제로 인해 스킵 - vi.useFakeTimers(); - sel.value = 'p1'; - addBtn.click(); - await vi.advanceTimersByTimeAsync(80000); - vi.useRealTimers(); - }); + vi.useFakeTimers() + sel.value = 'p1' + addBtn.click() + await vi.advanceTimersByTimeAsync(80000) + vi.useRealTimers() + }) it.skip('추천할인 상품은 드롭다운에 💝 아이콘 표시', async () => { // 원본 코드의 타이머 구현 문제로 인해 스킵 - vi.useFakeTimers(); - sel.value = 'p1'; - addBtn.click(); - await vi.advanceTimersByTimeAsync(80000); - vi.useRealTimers(); - }); - }); + vi.useFakeTimers() + sel.value = 'p1' + addBtn.click() + await vi.advanceTimersByTimeAsync(80000) + vi.useRealTimers() + }) + }) describe('3.3.4 할인 중복', () => { it.skip('번개세일 + 추천할인 = 25% SUPER SALE', async () => { // 원본 코드의 타이머 구현 문제로 인해 스킵 - vi.useFakeTimers(); - await vi.advanceTimersByTimeAsync(40000); - sel.value = 'p1'; - addBtn.click(); - await vi.advanceTimersByTimeAsync(80000); - vi.useRealTimers(); - }); - }); - }); - }); + vi.useFakeTimers() + await vi.advanceTimersByTimeAsync(40000) + sel.value = 'p1' + addBtn.click() + await vi.advanceTimersByTimeAsync(80000) + vi.useRealTimers() + }) + }) + }) + }) // 4. 포인트 적립 시스템 테스트 describe('4. 포인트 적립 시스템', () => { describe('4.1 기본 적립', () => { it('최종 결제 금액의 0.1% 포인트 적립', () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() // 10,000원 -> 10포인트 - expect(loyaltyPoints.textContent).toContain('10p'); - }); - }); + expect(loyaltyPoints.textContent).toContain('10p') + }) + }) describe('4.2 추가 적립', () => { it('화요일 구매 시 기본 포인트 2배', () => { - const tuesday = new Date('2024-10-15'); - vi.useFakeTimers(); - vi.setSystemTime(tuesday); + const tuesday = new Date('2024-10-15') + vi.useFakeTimers() + vi.setSystemTime(tuesday) - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() // 9,000원 (화요일 10% 할인) -> 9포인트 * 2 = 18포인트 - expect(loyaltyPoints.textContent).toContain('18p'); - expect(loyaltyPoints.textContent).toContain('화요일 2배'); + expect(loyaltyPoints.textContent).toContain('18p') + expect(loyaltyPoints.textContent).toContain('화요일 2배') - vi.useRealTimers(); - }); + vi.useRealTimers() + }) it('키보드+마우스 세트 구매 시 +50p', () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - sel.value = 'p2'; - addBtn.click(); + sel.value = 'p2' + addBtn.click() // 30,000원 -> 30포인트 + 50포인트 = 80포인트 - expect(loyaltyPoints.textContent).toContain('80p'); - expect(loyaltyPoints.textContent).toContain('키보드+마우스 세트'); - }); + expect(loyaltyPoints.textContent).toContain('80p') + expect(loyaltyPoints.textContent).toContain('키보드+마우스 세트') + }) it('풀세트(키보드+마우스+모니터암) 구매 시 +100p', () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - sel.value = 'p2'; - addBtn.click(); + sel.value = 'p2' + addBtn.click() - sel.value = 'p3'; - addBtn.click(); + sel.value = 'p3' + addBtn.click() // 60,000원 -> 60포인트 + 50포인트(세트) + 100포인트(풀세트) = 210포인트 - expect(loyaltyPoints.textContent).toContain('210p'); - expect(loyaltyPoints.textContent).toContain('풀세트 구매'); - }); + expect(loyaltyPoints.textContent).toContain('210p') + expect(loyaltyPoints.textContent).toContain('풀세트 구매') + }) it('수량별 보너스 - 10개 이상 +20p', () => { - addItemsToCart(sel, addBtn, 'p1', 10); + addItemsToCart(sel, addBtn, 'p1', 10) // 90,000원 (10% 할인) -> 90포인트 + 20포인트 = 110포인트 - expect(loyaltyPoints.textContent).toContain('110p'); - expect(loyaltyPoints.textContent).toContain('대량구매(10개+)'); - }); + expect(loyaltyPoints.textContent).toContain('110p') + expect(loyaltyPoints.textContent).toContain('대량구매(10개+)') + }) it('수량별 보너스 - 20개 이상 +50p', () => { - addItemsToCart(sel, addBtn, 'p1', 20); + addItemsToCart(sel, addBtn, 'p1', 20) // 180,000원 (10% 할인) -> 180포인트 + 50포인트 = 230포인트 - expect(loyaltyPoints.textContent).toContain('230p'); - expect(loyaltyPoints.textContent).toContain('대량구매(20개+)'); - }); + expect(loyaltyPoints.textContent).toContain('230p') + expect(loyaltyPoints.textContent).toContain('대량구매(20개+)') + }) it('수량별 보너스 - 30개 이상 +100p', () => { - addItemsToCart(sel, addBtn, 'p1', 30); + addItemsToCart(sel, addBtn, 'p1', 30) // 225,000원 (25% 할인) -> 225포인트 + 100포인트 = 325포인트 - expect(loyaltyPoints.textContent).toContain('325p'); - expect(loyaltyPoints.textContent).toContain('대량구매(30개+)'); - }); - }); + expect(loyaltyPoints.textContent).toContain('325p') + expect(loyaltyPoints.textContent).toContain('대량구매(30개+)') + }) + }) describe('4.3 포인트 표시', () => { it('포인트 적립 내역 상세 표시', () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - sel.value = 'p2'; - addBtn.click(); + sel.value = 'p2' + addBtn.click() - const pointsText = loyaltyPoints.textContent; - expect(pointsText).toContain('기본:'); - expect(pointsText).toContain('키보드+마우스 세트'); - }); - }); - }); + const pointsText = loyaltyPoints.textContent + expect(pointsText).toContain('기본:') + expect(pointsText).toContain('키보드+마우스 세트') + }) + }) + }) // 5. UI/UX 요구사항 테스트 describe('5. UI/UX 요구사항', () => { describe('5.1 레이아웃', () => { it('필수 레이아웃 요소가 존재해야 함', () => { // 헤더 - expect(document.querySelector('h1').textContent).toContain('🛒 Hanghae Online Store'); - expect(document.querySelector('.text-5xl').textContent).toContain('Shopping Cart'); + expect(document.querySelector('h1').textContent).toContain( + '🛒 Hanghae Online Store', + ) + expect(document.querySelector('.text-5xl').textContent).toContain( + 'Shopping Cart', + ) // 좌측: 상품 선택 및 장바구니 - expect(document.querySelector('#product-select')).toBeTruthy(); - expect(document.querySelector('#cart-items')).toBeTruthy(); + expect(document.querySelector('#product-select')).toBeTruthy() + expect(document.querySelector('#cart-items')).toBeTruthy() // 우측: 주문 요약 - expect(document.querySelector('#cart-total')).toBeTruthy(); - expect(document.querySelector('#loyalty-points')).toBeTruthy(); + expect(document.querySelector('#cart-total')).toBeTruthy() + expect(document.querySelector('#loyalty-points')).toBeTruthy() // 도움말 버튼 - const helpButton = document.querySelector('.fixed.top-4.right-4'); - expect(helpButton).toBeTruthy(); - }); - }); + const helpButton = document.querySelector('.fixed.top-4.right-4') + expect(helpButton).toBeTruthy() + }) + }) describe('5.2 상품 선택 영역', () => { it('할인 중인 상품 강조 표시 확인', async () => { // 현재 화요일 테스트 또는 일반 상황에서의 강조 표시만 확인 - const options = Array.from(sel.options); - + const options = Array.from(sel.options) + // 품절 상품이 비활성화되어 있는지 확인 - const disabledOption = options.find(opt => opt.disabled); + const disabledOption = options.find((opt) => opt.disabled) if (disabledOption) { - expect(disabledOption.textContent).toContain('품절'); + expect(disabledOption.textContent).toContain('품절') } - }); - }); + }) + }) describe('5.3 장바구니 영역', () => { it('장바구니 아이템 카드 형식 확인', () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - const cartItem = cartDisp.querySelector('#p1'); + const cartItem = cartDisp.querySelector('#p1') // 상품 이미지 - expect(cartItem.querySelector('.bg-gradient-black')).toBeTruthy(); + expect(cartItem.querySelector('.bg-gradient-black')).toBeTruthy() // 상품명 - expect(cartItem.querySelector('h3').textContent).toContain('버그 없애는 키보드'); + expect(cartItem.querySelector('h3').textContent).toContain( + '버그 없애는 키보드', + ) // 수량 조절 버튼 - expect(cartItem.querySelector('.quantity-change[data-change="1"]')).toBeTruthy(); - expect(cartItem.querySelector('.quantity-change[data-change="-1"]')).toBeTruthy(); + expect( + cartItem.querySelector('.quantity-change[data-change="1"]'), + ).toBeTruthy() + expect( + cartItem.querySelector('.quantity-change[data-change="-1"]'), + ).toBeTruthy() // 제거 버튼 - expect(cartItem.querySelector('.remove-item')).toBeTruthy(); - }); + expect(cartItem.querySelector('.remove-item')).toBeTruthy() + }) it('첫 번째 상품은 상단 여백 없음', () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - const firstItem = cartDisp.firstElementChild; - expect(firstItem.className).toContain('first:pt-0'); - }); + const firstItem = cartDisp.firstElementChild + expect(firstItem.className).toContain('first:pt-0') + }) it('마지막 상품은 하단 테두리 없음', () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - const lastItem = cartDisp.lastElementChild; - expect(lastItem.className).toContain('last:border-b-0'); - }); - }); + const lastItem = cartDisp.lastElementChild + expect(lastItem.className).toContain('last:border-b-0') + }) + }) describe('5.5 도움말 모달', () => { it('도움말 버튼 클릭 시 모달 표시', async () => { - const helpButton = document.querySelector('.fixed.top-4.right-4'); - const modal = document.querySelector('.fixed.inset-0'); - const slidePanel = document.querySelector('.fixed.right-0.top-0'); + const helpButton = document.querySelector('.fixed.top-4.right-4') + const modal = document.querySelector('.fixed.inset-0') + const slidePanel = document.querySelector('.fixed.right-0.top-0') // 초기 상태: 숨김 - expect(modal.classList.contains('hidden')).toBe(true); - expect(slidePanel.classList.contains('translate-x-full')).toBe(true); + expect(modal.classList.contains('hidden')).toBe(true) + expect(slidePanel.classList.contains('translate-x-full')).toBe(true) // 클릭 후: 표시 - await userEvent.click(helpButton); + await userEvent.click(helpButton) - expect(modal.classList.contains('hidden')).toBe(false); - expect(slidePanel.classList.contains('translate-x-full')).toBe(false); - }); + expect(modal.classList.contains('hidden')).toBe(false) + expect(slidePanel.classList.contains('translate-x-full')).toBe(false) + }) it('배경 클릭 시 모달 닫기', async () => { - const helpButton = document.querySelector('.fixed.top-4.right-4'); - const modal = document.querySelector('.fixed.inset-0'); + const helpButton = document.querySelector('.fixed.top-4.right-4') + const modal = document.querySelector('.fixed.inset-0') // 모달 열기 - await userEvent.click(helpButton); - expect(modal.classList.contains('hidden')).toBe(false); + await userEvent.click(helpButton) + expect(modal.classList.contains('hidden')).toBe(false) // 배경 클릭으로 닫기 - await userEvent.click(modal); - expect(modal.classList.contains('hidden')).toBe(true); - }); - }); - }); + await userEvent.click(modal) + expect(modal.classList.contains('hidden')).toBe(true) + }) + }) + }) // 6. 기능 요구사항 테스트 describe('6. 기능 요구사항', () => { describe('6.1 상품 추가', () => { it('선택한 상품을 장바구니에 추가', () => { - sel.value = 'p2'; - addBtn.click(); + sel.value = 'p2' + addBtn.click() - expect(cartDisp.children.length).toBe(1); - expect(cartDisp.querySelector('#p2')).toBeTruthy(); - }); + expect(cartDisp.children.length).toBe(1) + expect(cartDisp.querySelector('#p2')).toBeTruthy() + }) it('이미 있는 상품은 수량 증가', () => { - sel.value = 'p3'; - addBtn.click(); - addBtn.click(); + sel.value = 'p3' + addBtn.click() + addBtn.click() - expect(cartDisp.children.length).toBe(1); - const qty = cartDisp.querySelector('.quantity-number').textContent; - expect(qty).toBe('2'); - }); + expect(cartDisp.children.length).toBe(1) + const qty = cartDisp.querySelector('.quantity-number').textContent + expect(qty).toBe('2') + }) it('재고 초과 시 알림 표시', () => { // 재고가 10개인 상품5를 11개 추가 시도 - addItemsToCart(sel, addBtn, 'p5', 11); - + addItemsToCart(sel, addBtn, 'p5', 11) + // 장바구니에는 10개만 있어야 함 - const qty = getCartItemQuantity(cartDisp, 'p5'); - expect(qty).toBeLessThanOrEqual(10); - }); + const qty = getCartItemQuantity(cartDisp, 'p5') + expect(qty).toBeLessThanOrEqual(10) + }) it('품절 상품은 선택 불가', () => { - sel.value = 'p4'; - addBtn.click(); + sel.value = 'p4' + addBtn.click() - expect(cartDisp.children.length).toBe(0); - }); - }); + expect(cartDisp.children.length).toBe(0) + }) + }) describe('6.2 수량 변경', () => { it('+/- 버튼으로 수량 조절', async () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - const increaseBtn = cartDisp.querySelector('.quantity-change[data-change="1"]'); - const decreaseBtn = cartDisp.querySelector('.quantity-change[data-change="-1"]'); + const increaseBtn = cartDisp.querySelector( + '.quantity-change[data-change="1"]', + ) + const decreaseBtn = cartDisp.querySelector( + '.quantity-change[data-change="-1"]', + ) // 증가 - await userEvent.click(increaseBtn); - expect(cartDisp.querySelector('.quantity-number').textContent).toBe('2'); + await userEvent.click(increaseBtn) + expect(cartDisp.querySelector('.quantity-number').textContent).toBe( + '2', + ) // 감소 - await userEvent.click(decreaseBtn); - expect(cartDisp.querySelector('.quantity-number').textContent).toBe('1'); - }); + await userEvent.click(decreaseBtn) + expect(cartDisp.querySelector('.quantity-number').textContent).toBe( + '1', + ) + }) it('재고 한도 내에서만 증가 가능', async () => { // 재고 10개인 상품5를 10개 추가 - addItemsToCart(sel, addBtn, 'p5', 10); + addItemsToCart(sel, addBtn, 'p5', 10) - const increaseBtn = cartDisp.querySelector('.quantity-change[data-change="1"]'); - const qtyBefore = getCartItemQuantity(cartDisp, 'p5'); - - await userEvent.click(increaseBtn); - - const qtyAfter = getCartItemQuantity(cartDisp, 'p5'); - expect(qtyAfter).toBe(qtyBefore); // 수량이 증가하지 않아야 함 - }); + const increaseBtn = cartDisp.querySelector( + '.quantity-change[data-change="1"]', + ) + const qtyBefore = getCartItemQuantity(cartDisp, 'p5') + + await userEvent.click(increaseBtn) + + const qtyAfter = getCartItemQuantity(cartDisp, 'p5') + expect(qtyAfter).toBe(qtyBefore) // 수량이 증가하지 않아야 함 + }) it('수량 0이 되면 자동 제거', async () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - const decreaseBtn = cartDisp.querySelector('.quantity-change[data-change="-1"]'); - await userEvent.click(decreaseBtn); + const decreaseBtn = cartDisp.querySelector( + '.quantity-change[data-change="-1"]', + ) + await userEvent.click(decreaseBtn) - expect(cartDisp.children.length).toBe(0); - }); - }); + expect(cartDisp.children.length).toBe(0) + }) + }) describe('6.3 상품 제거', () => { it('Remove 버튼 클릭 시 즉시 제거', async () => { - sel.value = 'p2'; - addBtn.click(); + sel.value = 'p2' + addBtn.click() - const removeBtn = cartDisp.querySelector('.remove-item'); - await userEvent.click(removeBtn); + const removeBtn = cartDisp.querySelector('.remove-item') + await userEvent.click(removeBtn) - expect(cartDisp.children.length).toBe(0); - }); + expect(cartDisp.children.length).toBe(0) + }) it.skip('제거된 수량만큼 재고 복구', async () => { // 원본 코드의 재고 업데이트 버그로 인해 스킵 - addItemsToCart(sel, addBtn, 'p5', 5); + addItemsToCart(sel, addBtn, 'p5', 5) + + const removeBtn = cartDisp.querySelector('.remove-item') + await userEvent.click(removeBtn) - const removeBtn = cartDisp.querySelector('.remove-item'); - await userEvent.click(removeBtn); - // 재고가 복구되어야 하지만 원본 코드에서는 제대로 업데이트되지 않음 - }); - }); + }) + }) describe('6.4 실시간 계산', () => { it('수량 변경 시 즉시 재계산', async () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - expect(sum.textContent).toContain('₩10,000'); + expect(sum.textContent).toContain('₩10,000') - const increaseBtn = cartDisp.querySelector('.quantity-change[data-change="1"]'); - await userEvent.click(increaseBtn); + const increaseBtn = cartDisp.querySelector( + '.quantity-change[data-change="1"]', + ) + await userEvent.click(increaseBtn) - expect(sum.textContent).toContain('₩20,000'); - }); + expect(sum.textContent).toContain('₩20,000') + }) it('할인 정책 자동 적용', () => { - addItemsToCart(sel, addBtn, 'p1', 10); + addItemsToCart(sel, addBtn, 'p1', 10) - expect(discountInfo.textContent).toContain('10.0%'); - expect(sum.textContent).toContain('₩90,000'); - }); + expect(discountInfo.textContent).toContain('10.0%') + expect(sum.textContent).toContain('₩90,000') + }) it('포인트 실시간 업데이트', async () => { - sel.value = 'p1'; - addBtn.click(); + sel.value = 'p1' + addBtn.click() - expect(loyaltyPoints.textContent).toContain('10p'); + expect(loyaltyPoints.textContent).toContain('10p') - const increaseBtn = cartDisp.querySelector('.quantity-change[data-change="1"]'); - await userEvent.click(increaseBtn); + const increaseBtn = cartDisp.querySelector( + '.quantity-change[data-change="1"]', + ) + await userEvent.click(increaseBtn) - expect(loyaltyPoints.textContent).toContain('20p'); - }); - }); + expect(loyaltyPoints.textContent).toContain('20p') + }) + }) describe('6.5 상태 관리', () => { it('장바구니 상품 수 표시', () => { - expect(itemCount.textContent).toContain('0 items'); + expect(itemCount.textContent).toContain('0 items') - addItemsToCart(sel, addBtn, 'p1', 5); + addItemsToCart(sel, addBtn, 'p1', 5) - expect(itemCount.textContent).toContain('5 items'); - }); + expect(itemCount.textContent).toContain('5 items') + }) it('재고 부족/품절 상태 표시', () => { // 상품5를 재고 부족 상태로 만듦 - addItemsToCart(sel, addBtn, 'p5', 6); + addItemsToCart(sel, addBtn, 'p5', 6) - expect(stockInfo.textContent).toContain('재고 부족'); - expect(stockInfo.textContent).toContain('4개 남음'); + expect(stockInfo.textContent).toContain('재고 부족') + expect(stockInfo.textContent).toContain('4개 남음') // 상품4는 품절 - expect(stockInfo.textContent).toContain('에러 방지 노트북 파우치: 품절'); - }); - }); - }); + expect(stockInfo.textContent).toContain( + '에러 방지 노트북 파우치: 품절', + ) + }) + }) + }) // 8. 예외 처리 테스트 describe('8. 예외 처리', () => { describe('8.1 재고 부족', () => { it('장바구니 추가 시 재고 확인', () => { // 재고 10개인 상품을 11개 추가 시도 - addItemsToCart(sel, addBtn, 'p5', 11); - + addItemsToCart(sel, addBtn, 'p5', 11) + // 장바구니에는 최대 재고 수량만큼만 담김 - const qty = getCartItemQuantity(cartDisp, 'p5'); - expect(qty).toBeLessThanOrEqual(10); - }); + const qty = getCartItemQuantity(cartDisp, 'p5') + expect(qty).toBeLessThanOrEqual(10) + }) it('수량 증가 시 재고 확인', async () => { - addItemsToCart(sel, addBtn, 'p5', 10); + addItemsToCart(sel, addBtn, 'p5', 10) - const increaseBtn = cartDisp.querySelector('.quantity-change[data-change="1"]'); - await userEvent.click(increaseBtn); + const increaseBtn = cartDisp.querySelector( + '.quantity-change[data-change="1"]', + ) + await userEvent.click(increaseBtn) - expect(window.alert).toHaveBeenCalledWith('재고가 부족합니다.'); - }); - }); + expect(window.alert).toHaveBeenCalledWith('재고가 부족합니다.') + }) + }) describe('8.2 빈 장바구니', () => { it('장바구니가 비어있을 때 포인트 섹션 숨김', () => { - expect(cartDisp.children.length).toBe(0); - expect(loyaltyPoints.style.display).toBe('none'); - }); + expect(cartDisp.children.length).toBe(0) + expect(loyaltyPoints.style.display).toBe('none') + }) it('주문 요약에 기본값 표시', () => { - expect(sum.textContent).toContain('₩0'); - expect(itemCount.textContent).toContain('0 items'); - }); - }); + expect(sum.textContent).toContain('₩0') + expect(itemCount.textContent).toContain('0 items') + }) + }) describe('8.3 동시성 이슈', () => { it.skip('번개세일과 추천할인이 같은 상품에 적용 시 최대 25%', async () => { // 원본 코드의 타이머 구현 문제로 인해 스킵 - vi.useFakeTimers(); - await vi.advanceTimersByTimeAsync(40000); - sel.value = 'p1'; - addBtn.click(); - await vi.advanceTimersByTimeAsync(80000); - vi.useRealTimers(); - }); - }); - }); + vi.useFakeTimers() + await vi.advanceTimersByTimeAsync(40000) + sel.value = 'p1' + addBtn.click() + await vi.advanceTimersByTimeAsync(80000) + vi.useRealTimers() + }) + }) + }) // 복잡한 시나리오 테스트 describe('복잡한 통합 시나리오', () => { it('화요일 + 풀세트 + 대량구매 시나리오', () => { - const tuesday = new Date('2024-10-15'); - vi.useFakeTimers(); - vi.setSystemTime(tuesday); + const tuesday = new Date('2024-10-15') + vi.useFakeTimers() + vi.setSystemTime(tuesday) // 키보드 10개, 마우스 10개, 모니터암 10개 - addItemsToCart(sel, addBtn, 'p1', 10); - addItemsToCart(sel, addBtn, 'p2', 10); - addItemsToCart(sel, addBtn, 'p3', 10); + addItemsToCart(sel, addBtn, 'p1', 10) + addItemsToCart(sel, addBtn, 'p2', 10) + addItemsToCart(sel, addBtn, 'p3', 10) // 총액 확인: 600,000원 -> 25% 할인 -> 450,000원 -> 화요일 10% -> 405,000원 - expect(sum.textContent).toContain('₩405,000'); + expect(sum.textContent).toContain('₩405,000') // 포인트 확인: 405포인트(기본) * 2(화요일) + 50(세트) + 100(풀세트) + 100(30개) = 1060포인트 - expect(loyaltyPoints.textContent).toContain('1060p'); + expect(loyaltyPoints.textContent).toContain('1060p') - vi.useRealTimers(); - }); + vi.useRealTimers() + }) it.skip('번개세일 + 추천할인 + 화요일 시나리오', async () => { // 원본 코드의 타이머 구현 문제로 인해 스킵 - const tuesday = new Date('2024-10-15'); - vi.useFakeTimers(); - vi.setSystemTime(tuesday); - - await vi.advanceTimersByTimeAsync(40000); - sel.value = 'p1'; - addBtn.click(); - await vi.advanceTimersByTimeAsync(80000); - - vi.useRealTimers(); - }); - }); - }); -}); \ No newline at end of file + const tuesday = new Date('2024-10-15') + vi.useFakeTimers() + vi.setSystemTime(tuesday) + + await vi.advanceTimersByTimeAsync(40000) + sel.value = 'p1' + addBtn.click() + await vi.advanceTimersByTimeAsync(80000) + + vi.useRealTimers() + }) + }) + }) +}) diff --git a/src/basic/components/CartItem.js b/src/basic/components/CartItem.js new file mode 100644 index 000000000..9fe667c93 --- /dev/null +++ b/src/basic/components/CartItem.js @@ -0,0 +1,35 @@ +import { createElement } from '../utils/dom.js' +import { formatProductPrice, formatProductName } from '../utils/formatters.js' + +export function CartItem(product) { + const itemDiv = createElement( + 'div', + 'grid grid-cols-[80px_1fr_auto] gap-5 py-5 border-b border-gray-100 first:pt-0 last:border-b-0 last:pb-0', + ) + itemDiv.id = product.id + + const price = formatProductPrice(product) + const productName = formatProductName(product) + + itemDiv.innerHTML = ` +
+
+
+
+

${productName}

+

PRODUCT

+

${price}

+
+ + 1 + +
+
+
+
${price}
+ Remove +
+ ` + + return itemDiv +} diff --git a/src/basic/components/Header.js b/src/basic/components/Header.js new file mode 100644 index 000000000..ba151c9a0 --- /dev/null +++ b/src/basic/components/Header.js @@ -0,0 +1,11 @@ +import { createElement } from '../utils/dom' + +export function Header() { + const header = createElement('div', 'mb-8') + header.innerHTML = ` +

🛒 Hanghae Online Store

+
Shopping Cart
+

🛍️ 0 items in cart

+ ` + return header +} diff --git a/src/basic/components/HelpModal.js b/src/basic/components/HelpModal.js new file mode 100644 index 000000000..371d7dcb7 --- /dev/null +++ b/src/basic/components/HelpModal.js @@ -0,0 +1,102 @@ +// components/helpModal.js + +import { createElement } from '../utils/dom.js' + +export function HelpModal() { + const toggleButton = createElement( + 'button', + 'fixed top-4 right-4 bg-black text-white p-3 rounded-full hover:bg-gray-900 transition-colors z-50', + ) + toggleButton.innerHTML = ` + + + + ` + + const overlay = createElement( + 'div', + 'fixed inset-0 bg-black/50 z-40 hidden transition-opacity duration-300', + ) + + const panel = createElement( + 'div', + 'fixed right-0 top-0 h-full w-80 bg-white shadow-2xl p-6 overflow-y-auto z-50 transform translate-x-full transition-transform duration-300', + ) + panel.innerHTML = ` + +

📖 이용 안내

+
+

💰 할인 정책

+
+
+

개별 상품

+

+ • 키보드 10개↑: 10%
+ • 마우스 10개↑: 15%
+ • 모니터암 10개↑: 20%
+ • 스피커 10개↑: 25% +

+
+
+

전체 수량

+

• 30개 이상: 25%

+
+
+

특별 할인

+

+ • 화요일: +10%
+ • ⚡번개세일: 20%
+ • 💝추천할인: 5% +

+
+
+
+
+

🎁 포인트 적립

+
+
+

기본

+

• 구매액의 0.1%

+
+
+

추가

+

+ • 화요일: 2배
+ • 키보드+마우스: +50p
+ • 풀세트: +100p
+ • 10개↑: +20p / 20개↑: +50p / 30개↑: +100p +

+
+
+
+
+

💡 TIP

+

+ • 화요일 대량구매 = MAX 혜택
+ • ⚡+💝 중복 가능
+ • 상품4 = 품절 +

+
+ ` + + // 이벤트 핸들러 + toggleButton.onclick = function () { + overlay.classList.toggle('hidden') + panel.classList.toggle('translate-x-full') + } + + overlay.onclick = function (e) { + if (e.target === overlay) { + overlay.classList.add('hidden') + panel.classList.add('translate-x-full') + } + } + + overlay.appendChild(panel) + + return { toggleButton, overlay } +} diff --git a/src/basic/components/LeftColumn.js b/src/basic/components/LeftColumn.js new file mode 100644 index 000000000..17f7a33ca --- /dev/null +++ b/src/basic/components/LeftColumn.js @@ -0,0 +1,18 @@ +import { createElement } from '../utils/dom.js' +import { ProductContainer } from './ProductContainer.js' + +export function LeftColumn() { + const column = createElement( + 'div', + 'bg-white border border-gray-200 p-8 overflow-y-auto', + ) + + const container = ProductContainer() + column.appendChild(container) + + const cart = createElement('div') + cart.id = 'cart-items' + column.appendChild(cart) + + return column +} diff --git a/src/basic/components/OrderSummary.js b/src/basic/components/OrderSummary.js new file mode 100644 index 000000000..9778c1a4b --- /dev/null +++ b/src/basic/components/OrderSummary.js @@ -0,0 +1,37 @@ +// components/orderSummary.js + +import { createElement } from '../utils/dom.js' + +export function OrderSummary() { + const column = createElement('div', 'bg-black text-white p-8 flex flex-col') + column.innerHTML = ` +

Order Summary

+
+
+
+
+
+
+ Total +
₩0
+
+
적립 포인트: 0p
+
+ +
+
+ +

+ Free shipping on all orders.
+ Earn loyalty points with purchase. +

+ ` + return column +} diff --git a/src/basic/components/ProductContainer.js b/src/basic/components/ProductContainer.js new file mode 100644 index 000000000..af7aeaa3f --- /dev/null +++ b/src/basic/components/ProductContainer.js @@ -0,0 +1,32 @@ +// components/ProductContainer.js + +import { createElement } from '../utils/dom.js' + +export function ProductContainer() { + const container = createElement('div', 'mb-6 pb-6 border-b border-gray-200') + + const select = createElement( + 'select', + 'w-full p-3 border border-gray-300 rounded-lg text-base mb-3', + ) + select.id = 'product-select' + + const addButton = createElement( + 'button', + 'w-full py-3 bg-black text-white text-sm font-medium uppercase tracking-wider hover:bg-gray-800 transition-all', + ) + addButton.id = 'add-to-cart' + addButton.textContent = 'Add to Cart' + + const stockInfo = createElement( + 'div', + 'text-xs text-red-500 mt-3 whitespace-pre-line', + ) + stockInfo.id = 'stock-status' + + container.appendChild(select) + container.appendChild(addButton) + container.appendChild(stockInfo) + + return container +} diff --git a/src/basic/constants/index.js b/src/basic/constants/index.js new file mode 100644 index 000000000..7e4bdfcde --- /dev/null +++ b/src/basic/constants/index.js @@ -0,0 +1,45 @@ +export const PRODUCT_IDS = { + KEYBOARD: 'p1', + MOUSE: 'p2', + MONITOR_ARM: 'p3', + LAPTOP_POUCH: 'p4', + SPEAKER: 'p5', +} + +// 개별 상품 및 전체 할인율 +export const DISCOUNT_RATES = { + KEYBOARD: 0.1, // 10% + MOUSE: 0.15, // 15% + MONITOR_ARM: 0.2, // 20% + LAPTOP_POUCH: 0.05, // 5% + SPEAKER: 0.25, // 25% + BULK: 0.25, // 25% + TUESDAY: 0.1, // 10% + LIGHTNING: 0.2, // 20% + RECOMMEND: 0.05, // 5% +} + +export const THRESHOLDS = { + MIN_QUANTITY_FOR_DISCOUNT: 10, + MIN_QUANTITY_FOR_BULK: 30, + LOW_STOCK: 5, + TOTAL_STOCK_WARNING: 50, +} + +export const POINTS = { + RATE: 0.001, // 0.1% + BONUS: { + SET: 50, + FULL_SET: 100, + BULK_10: 20, + BULK_20: 50, + BULK_30: 100, + }, +} + +export const TIMERS = { + LIGHTNING_SALE_INTERVAL: 30000, + RECOMMEND_SALE_INTERVAL: 60000, + LIGHTNING_SALE_MAX_DELAY: 10000, + RECOMMEND_SALE_MAX_DELAY: 20000, +} diff --git a/src/basic/controllers/saleTimers.js b/src/basic/controllers/saleTimers.js new file mode 100644 index 000000000..dd021754d --- /dev/null +++ b/src/basic/controllers/saleTimers.js @@ -0,0 +1,100 @@ +import { DISCOUNT_RATES, TIMERS } from '../constants/index.js' +import { + getProducts, + getLastSelectedProductId, + updateProduct, + getCartItems, + getElements, +} from '../store/state.js' +import { updateProductOptions } from '../services/product.js' +import { updateCartItemPrice } from '../services/cart.js' +import { updateCart } from '../services/discount.js' + +export function setupSaleTimers() { + setupLightningSale() + setupRecommendSale() +} + +function setupLightningSale() { + const delay = Math.random() * TIMERS.LIGHTNING_SALE_MAX_DELAY + + setTimeout(() => { + setInterval(() => { + const products = getProducts() + const availableProducts = products.filter((p) => p.stock > 0 && !p.onSale) + + if (availableProducts.length === 0) return + + const randomProduct = + availableProducts[Math.floor(Math.random() * availableProducts.length)] + const newPrice = Math.round( + randomProduct.originalPrice * (1 - DISCOUNT_RATES.LIGHTNING), + ) + + updateProduct(randomProduct.id, { + price: newPrice, + onSale: true, + }) + + alert(`⚡번개세일! ${randomProduct.name}이(가) 20% 할인 중입니다!`) + + updateProductOptions() + updateCartPrices() + }, TIMERS.LIGHTNING_SALE_INTERVAL) + }, delay) +} + +function setupRecommendSale() { + const delay = Math.random() * TIMERS.RECOMMEND_SALE_MAX_DELAY + + setTimeout(() => { + setInterval(() => { + const cartItems = getCartItems() + const lastSelectedId = getLastSelectedProductId() + + if (Object.keys(cartItems).length === 0 || !lastSelectedId) return + + const products = getProducts() + const recommendableProducts = products.filter( + (p) => p.id !== lastSelectedId && p.stock > 0 && !p.recommendSale, + ) + + if (recommendableProducts.length === 0) return + + const recommendProduct = recommendableProducts[0] + const newPrice = Math.round( + recommendProduct.price * (1 - DISCOUNT_RATES.RECOMMEND), + ) + + alert( + `💝 ${recommendProduct.name}은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!`, + ) + + updateProduct(recommendProduct.id, { + price: newPrice, + recommendSale: true, + }) + + updateProductOptions() + updateCartPrices() + }, TIMERS.RECOMMEND_SALE_INTERVAL) + }, delay) +} + +function updateCartPrices() { + const elements = getElements() + const cartItemElements = elements.cartItems.children + + for (let i = 0; i < cartItemElements.length; i++) { + const itemElement = cartItemElements[i] + const productId = itemElement.id + const products = getProducts() + const product = products.find((p) => p.id === productId) + + if (product) { + updateCartItemPrice(itemElement, product) + } + } + + updateCart() +} diff --git a/src/basic/data/products.js b/src/basic/data/products.js new file mode 100644 index 000000000..d22951ba5 --- /dev/null +++ b/src/basic/data/products.js @@ -0,0 +1,51 @@ +import { PRODUCT_IDS } from '../constants/index.js' + +export function initializeProducts() { + return [ + { + id: PRODUCT_IDS.KEYBOARD, + name: '버그 없애는 키보드', + price: 10000, + originalPrice: 10000, + stock: 50, + onSale: false, + recommendSale: false, + }, + { + id: PRODUCT_IDS.MOUSE, + name: '생산성 폭발 마우스', + price: 20000, + originalPrice: 20000, + stock: 30, + onSale: false, + recommendSale: false, + }, + { + id: PRODUCT_IDS.MONITOR_ARM, + name: '거북목 탈출 모니터암', + price: 30000, + originalPrice: 30000, + stock: 20, + onSale: false, + recommendSale: false, + }, + { + id: PRODUCT_IDS.LAPTOP_POUCH, + name: '에러 방지 노트북 파우치', + price: 15000, + originalPrice: 15000, + stock: 0, + onSale: false, + recommendSale: false, + }, + { + id: PRODUCT_IDS.SPEAKER, + name: '코딩할 때 듣는 Lo-Fi 스피커', + price: 25000, + originalPrice: 25000, + stock: 10, + onSale: false, + recommendSale: false, + }, + ] +} diff --git a/src/basic/event/eventHandlers.js b/src/basic/event/eventHandlers.js new file mode 100644 index 000000000..65e26fce9 --- /dev/null +++ b/src/basic/event/eventHandlers.js @@ -0,0 +1,29 @@ +import { getElements } from '../store/state.js' +import { addToCart, changeQuantity, removeFromCart } from '../services/cart.js' + +// 이벤트 핸들러 설정 +export function setupEventHandlers() { + const elements = getElements() + + // 상품 추가 버튼 + elements.addButton.addEventListener('click', function () { + const selectedProductId = elements.productSelect.value + if (selectedProductId) { + addToCart(selectedProductId) + } + }) + + // 장바구니 클릭 이벤트 (이벤트 위임) + elements.cartItems.addEventListener('click', function (event) { + const target = event.target + + if (target.classList.contains('quantity-change')) { + const productId = target.dataset.productId + const change = parseInt(target.dataset.change) + changeQuantity(productId, change) + } else if (target.classList.contains('remove-item')) { + const productId = target.dataset.productId + removeFromCart(productId) + } + }) +} diff --git a/src/basic/main.basic.js b/src/basic/main.basic.js index 825eae3a5..441be02e4 100644 --- a/src/basic/main.basic.js +++ b/src/basic/main.basic.js @@ -1,763 +1,48 @@ -var prodList -var bonusPts = 0 -var stockInfo -var itemCnt -var lastSel -var sel -var addBtn -var totalAmt = 0 -var PRODUCT_ONE = 'p1' -var p2 = 'p2' -var product_3 = 'p3' -var p4 = "p4" -var PRODUCT_5 = `p5` -var cartDisp +import { initializeState, setElements } from './store/state.js' +import { createElement, initializeDOMElements } from './utils/dom.js' +import { LeftColumn } from './components/LeftColumn.js' +import { updateProductOptions } from './services/product.js' +import { OrderSummary } from './components/OrderSummary.js' +import { HelpModal } from './components/HelpModal.js' +import { Header } from './components/Header.js' +import { setupEventHandlers } from './event/eventHandlers.js' +import { updateCart } from './services/discount.js' +import { setupSaleTimers } from './controllers/saleTimers.js' + +// 메인 초기화 함수 function main() { - var root; - var header; - var gridContainer; - var leftColumn; - var selectorContainer; - var rightColumn; - var manualToggle; - var manualOverlay; - var manualColumn; - var lightningDelay; - totalAmt = 0; - itemCnt = 0; - lastSel = null; - prodList = [ - {id: PRODUCT_ONE, name: '버그 없애는 키보드', val: 10000, originalVal: 10000, q: 50, onSale: false, suggestSale: false}, - {id: p2, name: '생산성 폭발 마우스', val: 20000, originalVal: 20000, q: 30, onSale: false, suggestSale: false}, - {id: product_3, name: "거북목 탈출 모니터암", val: 30000, originalVal: 30000, q: 20, onSale: false, suggestSale: false}, - {id: p4, name: "에러 방지 노트북 파우치", val: 15000, originalVal: 15000, q: 0, onSale: false, suggestSale: false}, - { - id: PRODUCT_5, - name: `코딩할 때 듣는 Lo-Fi 스피커`, - val: 25000, - originalVal: 25000, - q: 10, - onSale: false, - suggestSale: false - } - ] - var root = document.getElementById('app') - header = document.createElement('div'); - header.className = 'mb-8' - header.innerHTML = ` -

🛒 Hanghae Online Store

-
Shopping Cart
-

🛍️ 0 items in cart

- `; - sel = document.createElement('select'); - sel.id = 'product-select'; - gridContainer = document.createElement('div'); - leftColumn = document.createElement("div"); - leftColumn['className'] = 'bg-white border border-gray-200 p-8 overflow-y-auto' - selectorContainer = document.createElement('div'); - selectorContainer.className = 'mb-6 pb-6 border-b border-gray-200'; - sel.className = 'w-full p-3 border border-gray-300 rounded-lg text-base mb-3'; - gridContainer.className = 'grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-6 flex-1 overflow-hidden'; - addBtn = document.createElement('button'); - stockInfo = document.createElement('div'); - addBtn.id = 'add-to-cart'; - stockInfo.id = 'stock-status'; - stockInfo.className = 'text-xs text-red-500 mt-3 whitespace-pre-line'; - addBtn.innerHTML = 'Add to Cart'; - addBtn.className = 'w-full py-3 bg-black text-white text-sm font-medium uppercase tracking-wider hover:bg-gray-800 transition-all'; - selectorContainer.appendChild(sel); - selectorContainer.appendChild(addBtn); - selectorContainer.appendChild(stockInfo); - leftColumn.appendChild(selectorContainer); - cartDisp = document.createElement('div'); - leftColumn.appendChild(cartDisp); - cartDisp.id = 'cart-items'; - rightColumn = document.createElement('div'); - rightColumn.className = 'bg-black text-white p-8 flex flex-col'; - rightColumn.innerHTML = ` -

Order Summary

-
-
-
-
-
-
- Total -
₩0
-
-
적립 포인트: 0p
-
- -
-
- -

- Free shipping on all orders.
- Earn loyalty points with purchase. -

- `; - sum = rightColumn.querySelector('#cart-total'); - manualToggle = document.createElement('button'); - manualToggle.onclick = function () { - manualOverlay.classList.toggle('hidden'); - manualColumn.classList.toggle('translate-x-full'); - }; - manualToggle.className = 'fixed top-4 right-4 bg-black text-white p-3 rounded-full hover:bg-gray-900 transition-colors z-50'; - manualToggle.innerHTML = ` - - - - `; - manualOverlay = document.createElement('div'); - manualOverlay.className = 'fixed inset-0 bg-black/50 z-40 hidden transition-opacity duration-300'; - manualOverlay.onclick = function (e) { - if (e.target === manualOverlay) { - manualOverlay.classList.add('hidden'); - manualColumn.classList.add('translate-x-full'); - } - }; - manualColumn = document.createElement('div'); - manualColumn.className = 'fixed right-0 top-0 h-full w-80 bg-white shadow-2xl p-6 overflow-y-auto z-50 transform translate-x-full transition-transform duration-300'; - manualColumn.innerHTML = ` - -

📖 이용 안내

-
-

💰 할인 정책

-
-
-

개별 상품

-

- • 키보드 10개↑: 10%
- • 마우스 10개↑: 15%
- • 모니터암 10개↑: 20%
- • 스피커 10개↑: 25% -

-
-
-

전체 수량

-

• 30개 이상: 25%

-
-
-

특별 할인

-

- • 화요일: +10%
- • ⚡번개세일: 20%
- • 💝추천할인: 5% -

-
-
-
-
-

🎁 포인트 적립

-
-
-

기본

-

• 구매액의 0.1%

-
-
-

추가

-

- • 화요일: 2배
- • 키보드+마우스: +50p
- • 풀세트: +100p
- • 10개↑: +20p / 20개↑: +50p / 30개↑: +100p -

-
-
-
-
-

💡 TIP

-

- • 화요일 대량구매 = MAX 혜택
- • ⚡+💝 중복 가능
- • 상품4 = 품절 -

-
- `; - gridContainer.appendChild(leftColumn); - gridContainer.appendChild(rightColumn); - manualOverlay.appendChild(manualColumn); - root.appendChild(header); - root.appendChild(gridContainer); - root.appendChild(manualToggle); - root.appendChild(manualOverlay); - var initStock = 0; - for (var i = 0; i < prodList.length; i++) { - initStock += prodList[i].q; - } - onUpdateSelectOptions(); - handleCalculateCartStuff(); - lightningDelay = Math.random() * 10000; - setTimeout(() => { - setInterval(function () { - var luckyIdx = Math.floor(Math.random() * prodList.length); - var luckyItem = prodList[luckyIdx]; - if (luckyItem.q > 0 && !luckyItem.onSale) { - luckyItem.val = Math.round(luckyItem.originalVal * 80 / 100); - luckyItem.onSale = true; - alert('⚡번개세일! ' + luckyItem.name + '이(가) 20% 할인 중입니다!'); - onUpdateSelectOptions(); - doUpdatePricesInCart(); - } - }, 30000); - }, lightningDelay); - setTimeout(function () { - setInterval(function () { - if (cartDisp.children.length === 0) { - } - if (lastSel) { - var suggest = null; - for (var k = 0; k < prodList.length; k++) { - if (prodList[k].id !== lastSel) { - if (prodList[k].q > 0) { - if (!prodList[k].suggestSale) { - suggest = prodList[k]; - break; - } - } - } - } - if (suggest) { - alert('💝 ' + suggest.name + '은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!'); - suggest.val = Math.round(suggest.val * (100 - 5) / 100); - suggest.suggestSale = true; - onUpdateSelectOptions(); - doUpdatePricesInCart(); - } - } - }, 60000); - }, Math.random() * 20000); -}; -var sum -function onUpdateSelectOptions() { - var totalStock; - var opt; - var discountText; - sel.innerHTML = ''; - totalStock = 0; - for (var idx = 0; idx < prodList.length; idx++) { - var _p = prodList[idx]; - totalStock = totalStock + _p.q; - } - for (var i = 0; i < prodList.length; i++) { - (function() { - var item = prodList[i]; - opt = document.createElement("option") - opt.value = item.id; - discountText = ''; - if (item.onSale) discountText += ' ⚡SALE'; - if (item.suggestSale) discountText += ' 💝추천'; - if (item.q === 0) { - opt.textContent = item.name + ' - ' + item.val + '원 (품절)' + discountText - opt.disabled = true - opt.className = 'text-gray-400'; - } else { - if (item.onSale && item.suggestSale) { - opt.textContent = '⚡💝' + item.name + ' - ' + item.originalVal + '원 → ' + item.val + '원 (25% SUPER SALE!)'; - opt.className = 'text-purple-600 font-bold'; - } else if (item.onSale) { - opt.textContent = '⚡' + item.name + ' - ' + item.originalVal + '원 → ' + item.val + '원 (20% SALE!)'; - opt.className = 'text-red-500 font-bold'; - } else if (item.suggestSale) { - opt.textContent = '💝' + item.name + ' - ' + item.originalVal + '원 → ' + item.val + '원 (5% 추천할인!)'; - opt.className = 'text-blue-500 font-bold'; - } else { - opt.textContent = item.name + ' - ' + item.val + '원' + discountText; - } - } - sel.appendChild(opt); - })(); - } - if (totalStock < 50) { - sel.style.borderColor = 'orange'; - } else { - sel.style.borderColor = ''; - } + // 전역 상태 초기화 + initializeState() + + // DOM 구조 생성 + const root = document.getElementById('app') + const header = Header() + const gridContainer = createElement( + 'div', + 'grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-6 flex-1 overflow-hidden', + ) + const leftColumn = LeftColumn() + const rightColumn = OrderSummary() + const { toggleButton, overlay } = HelpModal() + + gridContainer.appendChild(leftColumn) + gridContainer.appendChild(rightColumn) + + root.appendChild(header) + root.appendChild(gridContainer) + root.appendChild(toggleButton) + root.appendChild(overlay) + + // DOM 요소 참조 저장 + const elements = initializeDOMElements() + setElements(elements) + + // 초기화 + updateProductOptions() + updateCart() + setupEventHandlers() + setupSaleTimers() } -function handleCalculateCartStuff() { - var cartItems; - var subTot; - var itemDiscounts; - var lowStockItems; - var idx; - var originalTotal; - var bulkDisc; - var itemDisc; - var savedAmount; - var summaryDetails; - var totalDiv; - var loyaltyPointsDiv; - var points; - var discountInfoDiv; - var itemCountElement; - var previousCount; - var stockMsg; - var pts; - var hasP1; - var hasP2; - var loyaltyDiv; - totalAmt = 0; - itemCnt = 0; - originalTotal = totalAmt - cartItems = cartDisp.children; - subTot = 0; - bulkDisc = subTot; - itemDiscounts = []; - lowStockItems = []; - for (idx = 0; idx < prodList.length; idx++) { - if (prodList[idx].q < 5 && prodList[idx].q > 0) { - lowStockItems.push(prodList[idx].name); - } - } - for (let i = 0; i < cartItems.length; i++) { - (function () { - var curItem; - for (var j = 0; j < prodList.length; j++) { - if (prodList[j].id === cartItems[i].id) { - curItem = prodList[j]; - break; - } - } - var qtyElem = cartItems[i].querySelector('.quantity-number'); - var q; - var itemTot; - var disc; - q = parseInt(qtyElem.textContent); - itemTot = curItem.val * q; - disc = 0; - itemCnt += q; - subTot += itemTot; - var itemDiv = cartItems[i]; - var priceElems = itemDiv.querySelectorAll('.text-lg, .text-xs'); - priceElems.forEach(function (elem) { - if (elem.classList.contains('text-lg')) { - elem.style.fontWeight = q >= 10 ? 'bold' : 'normal'; - } - }); - if (q >= 10) { - if (curItem.id === PRODUCT_ONE) { - disc = 10 / 100; - } else { - if (curItem.id === p2) { - disc = 15 / 100; - } else { - if (curItem.id === product_3) { - disc = 20 / 100; - } else { - if (curItem.id === p4) { - disc = 5 / 100; - } else { - if (curItem.id === PRODUCT_5) { - disc = 25 / 100; - } - } - } - } - } - if (disc > 0) { - itemDiscounts.push({name: curItem.name, discount: disc * 100}); - } - } - totalAmt += itemTot * (1 - disc); - })(); - } - let discRate = 0; - var originalTotal = subTot; - if (itemCnt >= 30) { - totalAmt = subTot * 75 / 100; - discRate = 25 / 100; - } else { - discRate = (subTot - totalAmt) / subTot; - } - const today = new Date(); - var isTuesday = today.getDay() === 2; - var tuesdaySpecial = document.getElementById('tuesday-special'); - if (isTuesday) { - if (totalAmt > 0) { - totalAmt = totalAmt * 90 / 100; - discRate = 1 - (totalAmt / originalTotal); - tuesdaySpecial.classList.remove('hidden'); - } else { - tuesdaySpecial.classList.add('hidden'); - } - } else { - tuesdaySpecial.classList.add('hidden'); - } - document.getElementById('item-count').textContent = '🛍️ ' + itemCnt + ' items in cart'; - summaryDetails = document.getElementById('summary-details'); - summaryDetails.innerHTML = ''; - if (subTot > 0) { - for (let i = 0; i < cartItems.length; i++) { - var curItem; - for (var j = 0; j < prodList.length; j++) { - if (prodList[j].id === cartItems[i].id) { - curItem = prodList[j]; - break; - } - } - var qtyElem = cartItems[i].querySelector('.quantity-number'); - var q = parseInt(qtyElem.textContent); - var itemTotal = curItem.val * q; - summaryDetails.innerHTML += ` -
- ${curItem.name} x ${q} - ₩${itemTotal.toLocaleString()} -
- `; - } - summaryDetails.innerHTML += ` -
-
- Subtotal - ₩${subTot.toLocaleString()} -
- `; - if (itemCnt >= 30) { - summaryDetails.innerHTML += ` -
- 🎉 대량구매 할인 (30개 이상) - -25% -
- `; - } else if (itemDiscounts.length > 0) { - itemDiscounts.forEach(function (item) { - summaryDetails.innerHTML += ` -
- ${item.name} (10개↑) - -${item.discount}% -
- `; - }); - } - if (isTuesday) { - if (totalAmt > 0) { - summaryDetails.innerHTML += ` -
- 🌟 화요일 추가 할인 - -10% -
- `; - } - } - summaryDetails.innerHTML += ` -
- Shipping - Free -
- `; - } - totalDiv = sum.querySelector('.text-2xl'); - if (totalDiv) { - totalDiv.textContent = '₩' + Math.round(totalAmt).toLocaleString(); - } - loyaltyPointsDiv = document.getElementById('loyalty-points'); - if (loyaltyPointsDiv) { - points = Math.floor(totalAmt / 1000); - if (points > 0) { - loyaltyPointsDiv.textContent = '적립 포인트: ' + points + 'p'; - loyaltyPointsDiv.style.display = 'block'; - } else { - loyaltyPointsDiv.textContent = '적립 포인트: 0p'; - loyaltyPointsDiv.style.display = 'block'; - } - } - discountInfoDiv = document.getElementById('discount-info'); - discountInfoDiv.innerHTML = ''; - if (discRate > 0 && totalAmt > 0) { - savedAmount = originalTotal - totalAmt; - discountInfoDiv.innerHTML = ` -
-
- 총 할인율 - ${(discRate * 100).toFixed(1)}% -
-
₩${Math.round(savedAmount).toLocaleString()} 할인되었습니다
-
- `; - } - itemCountElement = document.getElementById('item-count'); - if (itemCountElement) { - previousCount = parseInt(itemCountElement.textContent.match(/\d+/) || 0); - itemCountElement.textContent = '🛍️ ' + itemCnt + ' items in cart'; - if (previousCount !== itemCnt) { - itemCountElement.setAttribute('data-changed', 'true'); - } - } - stockMsg = ''; - for (var stockIdx = 0; stockIdx < prodList.length; stockIdx++) { - var item = prodList[stockIdx]; - if (item.q < 5) { - if (item.q > 0) { - stockMsg = stockMsg + item.name + ': 재고 부족 (' + item.q + '개 남음)\n'; - } else { - stockMsg = stockMsg + item.name + ': 품절\n'; - } - } - } - stockInfo.textContent = stockMsg; - handleStockInfoUpdate(); - doRenderBonusPoints(); -} -var doRenderBonusPoints = function() { - var basePoints; - var finalPoints; - var pointsDetail; - var hasKeyboard; - var hasMouse; - var hasMonitorArm; - var nodes; - if (cartDisp.children.length === 0) { - document.getElementById('loyalty-points').style.display = 'none'; - return; - } - basePoints = Math.floor(totalAmt / 1000) - finalPoints = 0; - pointsDetail = []; - if (basePoints > 0) { - finalPoints = basePoints; - pointsDetail.push('기본: ' + basePoints + 'p'); - } - if (new Date().getDay() === 2) { - if (basePoints > 0) { - finalPoints = basePoints * 2; - pointsDetail.push('화요일 2배'); - } - } - hasKeyboard = false; - hasMouse = false; - hasMonitorArm = false; - nodes = cartDisp.children; - for (const node of nodes) { - var product = null; - for (var pIdx = 0; pIdx < prodList.length; pIdx++) { - if (prodList[pIdx].id === node.id) { - product = prodList[pIdx]; - break; - } - } - if (!product) continue; - if (product.id === PRODUCT_ONE) { - hasKeyboard = true; - } else if (product.id === p2) { - hasMouse = true; - } else if (product.id === product_3) { - hasMonitorArm = true; - } - } - if (hasKeyboard && hasMouse) { - finalPoints = finalPoints + 50; - pointsDetail.push('키보드+마우스 세트 +50p'); - } - if (hasKeyboard && hasMouse && hasMonitorArm) { - finalPoints = finalPoints + 100; - pointsDetail.push('풀세트 구매 +100p'); - } - if (itemCnt >= 30) { - finalPoints = finalPoints + 100; - pointsDetail.push('대량구매(30개+) +100p'); - } else { - if (itemCnt >= 20) { - finalPoints = finalPoints + 50; - pointsDetail.push('대량구매(20개+) +50p'); - } else { - if (itemCnt >= 10) { - finalPoints = finalPoints + 20; - pointsDetail.push('대량구매(10개+) +20p'); - } - } - } - bonusPts = finalPoints; - var ptsTag = document.getElementById('loyalty-points'); - if (ptsTag) { - if (bonusPts > 0) { - ptsTag.innerHTML = '
적립 포인트: ' + bonusPts + 'p
' + - '
' + pointsDetail.join(', ') + '
'; - ptsTag.style.display = 'block'; - } else { - ptsTag.textContent = '적립 포인트: 0p'; - ptsTag.style.display = 'block' - } - } -} -function onGetStockTotal() { - var sum; - var i; - var currentProduct; - sum = 0; - for (i = 0; i < prodList.length; i++) { - currentProduct = prodList[i]; - sum += currentProduct.q; - } - return sum; -} -var handleStockInfoUpdate = function() { - var infoMsg; - var totalStock; - var messageOptimizer; - infoMsg = ''; - totalStock = onGetStockTotal(); - if (totalStock < 30) { - } - prodList.forEach(function (item) { - if (item.q < 5) { - if (item.q > 0) { - infoMsg = infoMsg + item.name + ': 재고 부족 (' + item.q + '개 남음)\n'; - } else { - infoMsg = infoMsg + item.name + ': 품절\n'; - } - } - }); - stockInfo.textContent = infoMsg; -} -function doUpdatePricesInCart() { - var totalCount = 0, j = 0; - var cartItems; - while (cartDisp.children[j]) { - var qty = cartDisp.children[j].querySelector('.quantity-number'); - totalCount += qty ? parseInt(qty.textContent) : 0; - j++; - } - totalCount = 0; - for (j = 0; j < cartDisp.children.length; j++) { - totalCount += parseInt(cartDisp.children[j].querySelector('.quantity-number').textContent); - } - cartItems = cartDisp.children; - for (var i = 0; i < cartItems.length; i++) { - var itemId = cartItems[i].id; - var product = null; - for (var productIdx = 0; productIdx < prodList.length; productIdx++) { - if (prodList[productIdx].id === itemId) { - product = prodList[productIdx]; - break; - } - } - if (product) { - var priceDiv = cartItems[i].querySelector('.text-lg'); - var nameDiv = cartItems[i].querySelector('h3'); - if (product.onSale && product.suggestSale) { - priceDiv.innerHTML = '₩' + product.originalVal.toLocaleString() + ' ₩' + product.val.toLocaleString() + ''; - nameDiv.textContent = '⚡💝' + product.name; - } else if (product.onSale) { - priceDiv.innerHTML = '₩' + product.originalVal.toLocaleString() + ' ₩' + product.val.toLocaleString() + ''; - nameDiv.textContent = '⚡' + product.name; - } else if (product.suggestSale) { - priceDiv.innerHTML = '₩' + product.originalVal.toLocaleString() + ' ₩' + product.val.toLocaleString() + ''; - nameDiv.textContent = '💝' + product.name; - } else { - priceDiv.textContent = '₩' + product.val.toLocaleString(); - nameDiv.textContent = product.name; - } - } - } - handleCalculateCartStuff(); -} -main(); -addBtn.addEventListener("click", function () { - var selItem = sel.value - var hasItem = false; - for (var idx = 0; idx < prodList.length; idx++) { - if (prodList[idx].id === selItem) { - hasItem = true; - break; - } - } - if (!selItem || !hasItem) { - return; - } - var itemToAdd = null; - for (var j = 0; j < prodList.length; j++) { - if (prodList[j].id === selItem) { - itemToAdd = prodList[j]; - break; - } - } - if (itemToAdd && itemToAdd.q > 0) { - var item = document.getElementById(itemToAdd['id']); - if (item) { - var qtyElem = item.querySelector('.quantity-number') - var newQty = parseInt(qtyElem['textContent']) + 1 - if (newQty <= itemToAdd.q + parseInt(qtyElem.textContent)) { - qtyElem.textContent = newQty; - itemToAdd['q']-- - } else { - alert('재고가 부족합니다.'); - } - } else { - var newItem = document.createElement('div'); - newItem.id = itemToAdd.id; - newItem.className = 'grid grid-cols-[80px_1fr_auto] gap-5 py-5 border-b border-gray-100 first:pt-0 last:border-b-0 last:pb-0'; - newItem.innerHTML = ` -
-
-
-
-

${itemToAdd.onSale && itemToAdd.suggestSale ? '⚡💝' : itemToAdd.onSale ? '⚡' : itemToAdd.suggestSale ? '💝' : ''}${itemToAdd.name}

-

PRODUCT

-

${itemToAdd.onSale || itemToAdd.suggestSale ? '₩' + itemToAdd.originalVal.toLocaleString() + ' ₩' + itemToAdd.val.toLocaleString() + '' : '₩' + itemToAdd.val.toLocaleString()}

-
- - 1 - -
-
-
-
${itemToAdd.onSale || itemToAdd.suggestSale ? '₩' + itemToAdd.originalVal.toLocaleString() + ' ₩' + itemToAdd.val.toLocaleString() + '' : '₩' + itemToAdd.val.toLocaleString()}
- Remove -
- `; - cartDisp.appendChild(newItem); - itemToAdd.q--; - } - handleCalculateCartStuff(); - lastSel = selItem; - } -}); -cartDisp.addEventListener("click", function (event) { - var tgt = event.target; - if (tgt.classList.contains('quantity-change') || tgt.classList.contains("remove-item")) { - var prodId = tgt.dataset.productId; - var itemElem = document.getElementById(prodId) - var prod = null; - for (var prdIdx = 0; prdIdx < prodList.length; prdIdx++) { - if (prodList[prdIdx].id === prodId) { - prod = prodList[prdIdx]; - break; - } - } - if (tgt.classList.contains('quantity-change')) { - var qtyChange = parseInt(tgt.dataset.change); - var qtyElem = itemElem.querySelector('.quantity-number'); - var currentQty = parseInt(qtyElem.textContent); - var newQty = currentQty + qtyChange; - if (newQty > 0 && newQty <= prod.q + currentQty) { - qtyElem.textContent = newQty; - prod.q -= qtyChange; - } else if (newQty <= 0) { - prod.q += currentQty; - itemElem.remove(); - } else { - alert('재고가 부족합니다.'); - } - } else if (tgt.classList.contains('remove-item')) { - var qtyElem = itemElem.querySelector('.quantity-number'); - var remQty = parseInt(qtyElem.textContent); - prod.q += remQty; - itemElem.remove(); - } - if (prod && prod.q < 5) { - } - handleCalculateCartStuff(); - onUpdateSelectOptions(); - } -}); \ No newline at end of file + +// 앱 시작 +main() diff --git a/src/basic/services/cart.js b/src/basic/services/cart.js new file mode 100644 index 000000000..0fe6f44f5 --- /dev/null +++ b/src/basic/services/cart.js @@ -0,0 +1,94 @@ +// services/cart.js + +import { + getProduct, + getCartQuantity, + setCartQuantity, + setLastSelectedProductId, + updateProduct, + getElements, +} from '../store/state.js' +import { formatProductPrice, formatProductName } from '../utils/formatters.js' +import { CartItem } from '../components/CartItem.js' +import { updateCart } from './discount.js' +import { updateProductOptions } from '../services/product.js' + +export function updateCartItemPrice(itemElement, product) { + const priceDiv = itemElement.querySelector('.text-lg') + const nameDiv = itemElement.querySelector('h3') + + priceDiv.innerHTML = formatProductPrice(product) + nameDiv.textContent = formatProductName(product) +} + +export function addToCart(productId) { + const product = getProduct(productId) + if (!product || product.stock === 0) return + + const elements = getElements() + const existingItem = document.getElementById(productId) + + if (existingItem) { + // 이미 장바구니에 있는 경우 + const qtyElement = existingItem.querySelector('.quantity-number') + const currentDisplayQty = parseInt(qtyElement.textContent) + + if (product.stock > 0) { + qtyElement.textContent = currentDisplayQty + 1 + setCartQuantity(productId, currentDisplayQty + 1) + updateProduct(productId, { stock: product.stock - 1 }) + } else { + alert('재고가 부족합니다.') + return // 추가하지 않고 종료 + } + } else { + // 새로 추가하는 경우 + const cartItemElement = CartItem(product) + elements.cartItems.appendChild(cartItemElement) + setCartQuantity(productId, 1) + updateProduct(productId, { stock: product.stock - 1 }) + } + + setLastSelectedProductId(productId) + updateCart() + updateProductOptions() +} + +export function changeQuantity(productId, change) { + const product = getProduct(productId) + const itemElement = document.getElementById(productId) + const qtyElement = itemElement.querySelector('.quantity-number') + const currentQty = parseInt(qtyElement.textContent) + const newQty = currentQty + change + + if (newQty > 0) { + if (change > 0 && product.stock === 0) { + alert('재고가 부족합니다.') + return + } + + qtyElement.textContent = newQty + setCartQuantity(productId, newQty) + updateProduct(productId, { stock: product.stock - change }) + } else { + // 수량이 0이 되면 제거 + removeFromCart(productId) + return + } + + updateCart() + updateProductOptions() +} + +export function removeFromCart(productId) { + const product = getProduct(productId) + const itemElement = document.getElementById(productId) + const quantity = getCartQuantity(productId) + + updateProduct(productId, { stock: product.stock + quantity }) + setCartQuantity(productId, 0) + itemElement.remove() + + updateCart() + updateProductOptions() +} diff --git a/src/basic/services/discount.js b/src/basic/services/discount.js new file mode 100644 index 000000000..7544ee0b4 --- /dev/null +++ b/src/basic/services/discount.js @@ -0,0 +1,157 @@ +// services/discount.js + +import { PRODUCT_IDS, DISCOUNT_RATES, THRESHOLDS } from '../constants/index.js' +import { + getProduct, + getCartItems, + setTotalAmount, + setTotalQuantity, + getElements, +} from '../store/state.js' +import { calculatePoints } from './points.js' +import { updateStockStatus } from './product.js' +import { + updateSummaryDetails, + updateTotal, + updateDiscountInfo, +} from './orderSummary.js' +import { formatItemCount } from '../utils/formatters.js' +import { showElement, hideElement } from '../utils/dom.js' + +export function calculateItemDiscount(productId, quantity) { + if (quantity < THRESHOLDS.MIN_QUANTITY_FOR_DISCOUNT) return 0 + + const discountMap = { + [PRODUCT_IDS.KEYBOARD]: DISCOUNT_RATES.KEYBOARD, + [PRODUCT_IDS.MOUSE]: DISCOUNT_RATES.MOUSE, + [PRODUCT_IDS.MONITOR_ARM]: DISCOUNT_RATES.MONITOR_ARM, + [PRODUCT_IDS.LAPTOP_POUCH]: DISCOUNT_RATES.LAPTOP_POUCH, + [PRODUCT_IDS.SPEAKER]: DISCOUNT_RATES.SPEAKER, + } + + return discountMap[productId] || 0 +} + +export function calculateTotalWithDiscounts(cartItems) { + let subtotal = 0 + let totalWithItemDiscounts = 0 + let itemDiscounts = [] + let totalQuantity = 0 + + Object.entries(cartItems).forEach(([productId, quantity]) => { + const product = getProduct(productId) + if (!product) return + + totalQuantity += quantity + const itemTotal = product.price * quantity + subtotal += itemTotal + + const discountRate = calculateItemDiscount(productId, quantity) + if (discountRate > 0) { + itemDiscounts.push({ name: product.name, discount: discountRate * 100 }) + totalWithItemDiscounts += itemTotal * (1 - discountRate) + } else { + totalWithItemDiscounts += itemTotal + } + }) + + return { subtotal, totalWithItemDiscounts, itemDiscounts, totalQuantity } +} + +export function applyBulkDiscount(amount, totalQuantity) { + if (totalQuantity >= THRESHOLDS.MIN_QUANTITY_FOR_BULK) { + return amount * (1 - DISCOUNT_RATES.BULK) + } + return amount +} + +export function applyTuesdayDiscount(amount) { + const isTuesday = new Date().getDay() === 2 + if (isTuesday && amount > 0) { + return amount * (1 - DISCOUNT_RATES.TUESDAY) + } + return amount +} + +export function updateCart() { + const cartItems = getCartItems() + const elements = getElements() + + let totalAmount = 0 + let totalQuantity = 0 + let subtotal = 0 + let itemDiscounts = [] + + // 각 아이템별 계산 + Object.entries(cartItems).forEach(([productId, quantity]) => { + const product = getProduct(productId) + if (!product) return + + totalQuantity += quantity + const itemTotal = product.price * quantity + subtotal += itemTotal + + // 10개 이상일 때 개별 할인 적용 + let discountRate = 0 + if (quantity >= THRESHOLDS.MIN_QUANTITY_FOR_DISCOUNT) { + const discountMap = { + [PRODUCT_IDS.KEYBOARD]: DISCOUNT_RATES.KEYBOARD, + [PRODUCT_IDS.MOUSE]: DISCOUNT_RATES.MOUSE, + [PRODUCT_IDS.MONITOR_ARM]: DISCOUNT_RATES.MONITOR_ARM, + [PRODUCT_IDS.LAPTOP_POUCH]: DISCOUNT_RATES.LAPTOP_POUCH, + [PRODUCT_IDS.SPEAKER]: DISCOUNT_RATES.SPEAKER, + } + + discountRate = discountMap[productId] || 0 + if (discountRate > 0) { + itemDiscounts.push({ name: product.name, discount: discountRate * 100 }) + } + } + + totalAmount += itemTotal * (1 - discountRate) + + // 10개 이상일 때 가격 강조 + const cartItem = document.getElementById(productId) + if (cartItem) { + const priceElement = cartItem.querySelector('.text-lg') + if (priceElement) { + priceElement.style.fontWeight = quantity >= 10 ? 'bold' : 'normal' + } + } + }) + + // 대량 구매 할인 (30개 이상이면 개별 할인 무시하고 25% 적용) + const originalTotal = subtotal + if (totalQuantity >= THRESHOLDS.MIN_QUANTITY_FOR_BULK) { + totalAmount = subtotal * (1 - DISCOUNT_RATES.BULK) + } + + // 화요일 할인 + const isTuesday = new Date().getDay() === 2 + if (isTuesday && totalAmount > 0) { + totalAmount = totalAmount * (1 - DISCOUNT_RATES.TUESDAY) + showElement(elements.tuesdaySpecial) + } else { + hideElement(elements.tuesdaySpecial) + } + + // 할인율 계산 + const discountRate = + subtotal > 0 ? (originalTotal - totalAmount) / originalTotal : 0 + + // 상태 업데이트 + setTotalAmount(totalAmount) + setTotalQuantity(totalQuantity) + + // UI 업데이트 + elements.itemCount.textContent = formatItemCount(totalQuantity) + updateSummaryDetails(subtotal, itemDiscounts, isTuesday, totalQuantity) + updateTotal(totalAmount) + updateDiscountInfo(discountRate, originalTotal, totalAmount) + + // 포인트 계산 + calculatePoints() + + // 재고 상태 업데이트 + updateStockStatus() +} diff --git a/src/basic/services/orderSummary.js b/src/basic/services/orderSummary.js new file mode 100644 index 000000000..a174dded6 --- /dev/null +++ b/src/basic/services/orderSummary.js @@ -0,0 +1,106 @@ +import { getCartItems, getProduct, getElements } from '../store/state.js' +import { formatPrice, formatDiscountRate } from '../utils/formatters.js' +import { THRESHOLDS } from '../constants/index.js' + +export function updateSummaryDetails( + subtotal, + itemDiscounts, + isTuesday, + totalQuantity, +) { + const elements = getElements() + const summaryDetails = elements.summaryDetails + const cartItems = getCartItems() + + summaryDetails.innerHTML = '' + + if (subtotal === 0) return + + // 각 아이템 표시 + Object.entries(cartItems).forEach(([productId, quantity]) => { + const product = getProduct(productId) + if (!product) return + + const itemTotal = product.price * quantity + + summaryDetails.innerHTML += ` +
+ ${product.name} x ${quantity} + ${formatPrice(itemTotal)} +
+ ` + }) + + // 소계 + summaryDetails.innerHTML += ` +
+
+ Subtotal + ${formatPrice(subtotal)} +
+ ` + + // 할인 표시 - 대량구매 할인이 있으면 개별 할인 표시하지 않음 + if (totalQuantity >= THRESHOLDS.MIN_QUANTITY_FOR_BULK) { + summaryDetails.innerHTML += ` +
+ 🎉 대량구매 할인 (30개 이상) + -25% +
+ ` + } else if (itemDiscounts.length > 0) { + itemDiscounts.forEach((item) => { + summaryDetails.innerHTML += ` +
+ ${item.name} (10개↑) + -${item.discount}% +
+ ` + }) + } + + if (isTuesday && totalQuantity > 0) { + summaryDetails.innerHTML += ` +
+ 🌟 화요일 추가 할인 + -10% +
+ ` + } + + // 배송비 + summaryDetails.innerHTML += ` +
+ Shipping + Free +
+ ` +} + +export function updateTotal(amount) { + const elements = getElements() + const totalDiv = elements.cartTotal.querySelector('.text-2xl') + if (totalDiv) { + totalDiv.textContent = formatPrice(amount) + } +} + +export function updateDiscountInfo(discountRate, originalTotal, totalAmount) { + const elements = getElements() + const discountInfo = elements.discountInfo + + discountInfo.innerHTML = '' + + if (discountRate > 0 && totalAmount > 0) { + const savedAmount = originalTotal - totalAmount + discountInfo.innerHTML = ` +
+
+ 총 할인율 + ${formatDiscountRate(discountRate)} +
+
${formatPrice(savedAmount)} 할인되었습니다
+
+ ` + } +} diff --git a/src/basic/services/points.js b/src/basic/services/points.js new file mode 100644 index 000000000..c122fd58f --- /dev/null +++ b/src/basic/services/points.js @@ -0,0 +1,77 @@ +import { PRODUCT_IDS, POINTS } from '../constants/index.js' +import { + getTotalAmount, + getTotalQuantity, + getCartItems, + setBonusPoints, + getElements, +} from '../store/state.js' + +export function calculatePoints() { + const cartItems = getCartItems() + const elements = getElements() + + if (Object.keys(cartItems).length === 0) { + elements.loyaltyPoints.style.display = 'none' + return + } + + const totalAmount = getTotalAmount() + const totalQuantity = getTotalQuantity() + + // 기본 포인트는 총액을 1000으로 나눈 후 소수점 버림 + let basePoints = Math.floor(totalAmount / 1000) + let finalPoints = basePoints + let pointsDetail = [] + + if (basePoints > 0) { + pointsDetail.push(`기본: ${basePoints}p`) + } + + // 화요일 2배 적용 + const isTuesday = new Date().getDay() === 2 + if (isTuesday && basePoints > 0) { + finalPoints = basePoints * 2 + pointsDetail.push('화요일 2배') + } + + // 세트 보너스 + const hasKeyboard = cartItems[PRODUCT_IDS.KEYBOARD] + const hasMouse = cartItems[PRODUCT_IDS.MOUSE] + const hasMonitorArm = cartItems[PRODUCT_IDS.MONITOR_ARM] + + if (hasKeyboard && hasMouse) { + finalPoints += POINTS.BONUS.SET + pointsDetail.push('키보드+마우스 세트 +50p') + } + + if (hasKeyboard && hasMouse && hasMonitorArm) { + finalPoints += POINTS.BONUS.FULL_SET + pointsDetail.push('풀세트 구매 +100p') + } + + // 수량 보너스 + if (totalQuantity >= 30) { + finalPoints += POINTS.BONUS.BULK_30 + pointsDetail.push('대량구매(30개+) +100p') + } else if (totalQuantity >= 20) { + finalPoints += POINTS.BONUS.BULK_20 + pointsDetail.push('대량구매(20개+) +50p') + } else if (totalQuantity >= 10) { + finalPoints += POINTS.BONUS.BULK_10 + pointsDetail.push('대량구매(10개+) +20p') + } + + setBonusPoints(finalPoints) + + // UI 업데이트 + if (finalPoints > 0) { + elements.loyaltyPoints.innerHTML = + `
적립 포인트: ${finalPoints}p
` + + `
${pointsDetail.join(', ')}
` + elements.loyaltyPoints.style.display = 'block' + } else { + elements.loyaltyPoints.textContent = '적립 포인트: 0p' + elements.loyaltyPoints.style.display = 'block' + } +} diff --git a/src/basic/services/product.js b/src/basic/services/product.js new file mode 100644 index 000000000..9923cffc1 --- /dev/null +++ b/src/basic/services/product.js @@ -0,0 +1,65 @@ +import { createElement } from '../utils/dom.js' +import { formatProductOption } from '../utils/formatters.js' +import { getProducts, getElements } from '../store/state.js' +import { THRESHOLDS } from '../constants/index.js' + +export function updateProductOptions() { + const products = getProducts() + const elements = getElements() + const select = elements.productSelect + + // 현재 선택된 값 저장 + const currentValue = select.value + + select.innerHTML = '' + + // 전체 재고 계산 + const totalStock = products.reduce((sum, product) => sum + product.stock, 0) + + // 재고가 적을 때 테두리 색상 변경 + select.style.borderColor = + totalStock < THRESHOLDS.TOTAL_STOCK_WARNING ? 'orange' : '' + + products.forEach((product) => { + const option = createElement('option') + option.value = product.id + + // 할인 정보와 스타일 설정 + const { text, className } = formatProductOption(product) + option.textContent = text + option.className = className + + if (product.stock === 0) { + option.disabled = true + } + + select.appendChild(option) + }) + + // 이전에 선택된 값 복원 + if (currentValue) { + select.value = currentValue + } + + // 옵션 업데이트 시 재고 상태도 함께 업데이트 + updateStockStatus() +} + +export function updateStockStatus() { + const products = getProducts() + const elements = getElements() + let stockMessage = '' + + // 상품 순서대로 확인 (p1, p2, p3, p4, p5) + products.forEach((product) => { + if (product.stock < THRESHOLDS.LOW_STOCK) { + if (product.stock > 0) { + stockMessage += `${product.name}: 재고 부족 (${product.stock}개 남음)\n` + } else { + stockMessage += `${product.name}: 품절\n` + } + } + }) + + elements.stockInfo.textContent = stockMessage +} diff --git a/src/basic/store/state.js b/src/basic/store/state.js new file mode 100644 index 000000000..3a2ff2837 --- /dev/null +++ b/src/basic/store/state.js @@ -0,0 +1,101 @@ +// store/state.js + +import { initializeProducts } from '../data/products.js' + +// 전역 상태 +let state = { + products: [], + cartItems: {}, + lastSelectedProductId: null, + totalAmount: 0, + totalQuantity: 0, + bonusPoints: 0, + elements: {}, +} + +// 상태 초기화 +export function initializeState() { + state.products = initializeProducts() + state.cartItems = {} + state.lastSelectedProductId = null + state.totalAmount = 0 + state.totalQuantity = 0 + state.bonusPoints = 0 +} + +// Getters +export function getProducts() { + return state.products +} + +export function getProduct(productId) { + return state.products.find((p) => p.id === productId) +} + +export function getCartItems() { + return state.cartItems +} + +export function getCartQuantity(productId) { + return state.cartItems[productId] || 0 +} + +export function getLastSelectedProductId() { + return state.lastSelectedProductId +} + +export function getTotalAmount() { + return state.totalAmount +} + +export function getTotalQuantity() { + return state.totalQuantity +} + +export function getBonusPoints() { + return state.bonusPoints +} + +export function getElements() { + return state.elements +} + +// Setters +export function setProducts(products) { + state.products = products +} + +export function updateProduct(productId, updates) { + const product = state.products.find((p) => p.id === productId) + if (product) { + Object.assign(product, updates) + } +} + +export function setCartQuantity(productId, quantity) { + if (quantity > 0) { + state.cartItems[productId] = quantity + } else { + delete state.cartItems[productId] + } +} + +export function setLastSelectedProductId(productId) { + state.lastSelectedProductId = productId +} + +export function setTotalAmount(amount) { + state.totalAmount = amount +} + +export function setTotalQuantity(quantity) { + state.totalQuantity = quantity +} + +export function setBonusPoints(points) { + state.bonusPoints = points +} + +export function setElements(elements) { + state.elements = elements +} diff --git a/src/basic/utils/dom.js b/src/basic/utils/dom.js new file mode 100644 index 000000000..a22591085 --- /dev/null +++ b/src/basic/utils/dom.js @@ -0,0 +1,35 @@ +// utils/dom.js + +export function createElement(tag, className, innerHTML) { + const element = document.createElement(tag) + if (className) element.className = className + if (innerHTML) element.innerHTML = innerHTML + return element +} + +export function showElement(element) { + if (element) element.classList.remove('hidden') +} + +export function hideElement(element) { + if (element) element.classList.add('hidden') +} + +export function toggleElement(element) { + if (element) element.classList.toggle('hidden') +} + +export function initializeDOMElements() { + return { + productSelect: document.getElementById('product-select'), + addButton: document.getElementById('add-to-cart'), + cartItems: document.getElementById('cart-items'), + cartTotal: document.getElementById('cart-total'), + stockInfo: document.getElementById('stock-status'), + itemCount: document.getElementById('item-count'), + loyaltyPoints: document.getElementById('loyalty-points'), + discountInfo: document.getElementById('discount-info'), + tuesdaySpecial: document.getElementById('tuesday-special'), + summaryDetails: document.getElementById('summary-details'), + } +} diff --git a/src/basic/utils/formatters.js b/src/basic/utils/formatters.js new file mode 100644 index 000000000..5f46f2a89 --- /dev/null +++ b/src/basic/utils/formatters.js @@ -0,0 +1,82 @@ +// utils/formatters.js + +export function formatPrice(price) { + return `₩${Math.round(price).toLocaleString()}` +} + +export function formatDiscountRate(rate) { + return `${(rate * 100).toFixed(1)}%` +} + +export function formatStockMessage(productName, stock) { + if (stock === 0) { + return `${productName}: 품절` + } else if (stock < 5) { + return `${productName}: 재고 부족 (${stock}개 남음)` + } + return '' +} + +export function formatItemCount(count) { + return `🛍️ ${count} items in cart` +} + +export function formatPoints(points) { + return `${points}p` +} + +function determinePriceColorClass(onSale, recommendSale) { + if (onSale && recommendSale) return 'text-purple-600' + if (onSale) return 'text-red-500' + if (recommendSale) return 'text-blue-500' + return '' +} + +export function formatProductPrice(product) { + if (!product.onSale && !product.recommendSale) { + return formatPrice(product.price) + } + + const colorClass = determinePriceColorClass( + product.onSale, + product.recommendSale, + ) + const originalHtml = `${formatPrice(product.originalPrice)}` + const salePriceHtml = `${formatPrice(product.price)}` + + return `${originalHtml} ${salePriceHtml}` +} + +export function formatProductName(product) { + let saleIcon = '' + + if (product.onSale && product.recommendSale) { + saleIcon = '⚡💝' + } else if (product.onSale) { + saleIcon = '⚡' + } else if (product.recommendSale) { + saleIcon = '💝' + } + return saleIcon + product.name +} + +export function formatProductOption(product) { + let text = `${product.name} - ${product.price}원` + let className = '' + + if (product.stock === 0) { + text += ' (품절)' + className = 'text-gray-400' + } else if (product.onSale && product.recommendSale) { + text = `⚡💝${product.name} - ${product.originalPrice}원 → ${product.price}원 (25% SUPER SALE!)` + className = 'text-purple-600 font-bold' + } else if (product.onSale) { + text = `⚡${product.name} - ${product.originalPrice}원 → ${product.price}원 (20% SALE!)` + className = 'text-red-500 font-bold' + } else if (product.recommendSale) { + text = `💝${product.name} - ${product.originalPrice}원 → ${product.price}원 (5% 추천할인!)` + className = 'text-blue-500 font-bold' + } + + return { text, className } +} diff --git a/src/main.original.js b/src/main.original.js index 0fd12e7c9..37a4b7631 100644 --- a/src/main.original.js +++ b/src/main.original.js @@ -9,28 +9,60 @@ var totalAmt = 0 var PRODUCT_ONE = 'p1' var p2 = 'p2' var product_3 = 'p3' -var p4 = "p4" +var p4 = 'p4' var PRODUCT_5 = `p5` var cartDisp function main() { - var root; - var header; - var gridContainer; - var leftColumn; - var selectorContainer; - var rightColumn; - var manualToggle; - var manualOverlay; - var manualColumn; - var lightningDelay; - totalAmt = 0; - itemCnt = 0; - lastSel = null; + var root + var header + var gridContainer + var leftColumn + var selectorContainer + var rightColumn + var manualToggle + var manualOverlay + var manualColumn + var lightningDelay + totalAmt = 0 + itemCnt = 0 + lastSel = null prodList = [ - {id: PRODUCT_ONE, name: '버그 없애는 키보드', val: 10000, originalVal: 10000, q: 50, onSale: false, suggestSale: false}, - {id: p2, name: '생산성 폭발 마우스', val: 20000, originalVal: 20000, q: 30, onSale: false, suggestSale: false}, - {id: product_3, name: "거북목 탈출 모니터암", val: 30000, originalVal: 30000, q: 20, onSale: false, suggestSale: false}, - {id: p4, name: "에러 방지 노트북 파우치", val: 15000, originalVal: 15000, q: 0, onSale: false, suggestSale: false}, + { + id: PRODUCT_ONE, + name: '버그 없애는 키보드', + val: 10000, + originalVal: 10000, + q: 50, + onSale: false, + suggestSale: false, + }, + { + id: p2, + name: '생산성 폭발 마우스', + val: 20000, + originalVal: 20000, + q: 30, + onSale: false, + suggestSale: false, + }, + { + id: product_3, + name: '거북목 탈출 모니터암', + val: 30000, + originalVal: 30000, + q: 20, + onSale: false, + suggestSale: false, + }, + { + id: p4, + name: '에러 방지 노트북 파우치', + val: 15000, + originalVal: 15000, + q: 0, + onSale: false, + suggestSale: false, + }, { id: PRODUCT_5, name: `코딩할 때 듣는 Lo-Fi 스피커`, @@ -38,42 +70,45 @@ function main() { originalVal: 25000, q: 10, onSale: false, - suggestSale: false - } + suggestSale: false, + }, ] var root = document.getElementById('app') - header = document.createElement('div'); + header = document.createElement('div') header.className = 'mb-8' header.innerHTML = `

🛒 Hanghae Online Store

Shopping Cart

🛍️ 0 items in cart

- `; - sel = document.createElement('select'); - sel.id = 'product-select'; - gridContainer = document.createElement('div'); - leftColumn = document.createElement("div"); - leftColumn['className'] = 'bg-white border border-gray-200 p-8 overflow-y-auto' - selectorContainer = document.createElement('div'); - selectorContainer.className = 'mb-6 pb-6 border-b border-gray-200'; - sel.className = 'w-full p-3 border border-gray-300 rounded-lg text-base mb-3'; - gridContainer.className = 'grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-6 flex-1 overflow-hidden'; - addBtn = document.createElement('button'); - stockInfo = document.createElement('div'); - addBtn.id = 'add-to-cart'; - stockInfo.id = 'stock-status'; - stockInfo.className = 'text-xs text-red-500 mt-3 whitespace-pre-line'; - addBtn.innerHTML = 'Add to Cart'; - addBtn.className = 'w-full py-3 bg-black text-white text-sm font-medium uppercase tracking-wider hover:bg-gray-800 transition-all'; - selectorContainer.appendChild(sel); - selectorContainer.appendChild(addBtn); - selectorContainer.appendChild(stockInfo); - leftColumn.appendChild(selectorContainer); - cartDisp = document.createElement('div'); - leftColumn.appendChild(cartDisp); - cartDisp.id = 'cart-items'; - rightColumn = document.createElement('div'); - rightColumn.className = 'bg-black text-white p-8 flex flex-col'; + ` + sel = document.createElement('select') + sel.id = 'product-select' + gridContainer = document.createElement('div') + leftColumn = document.createElement('div') + leftColumn['className'] = + 'bg-white border border-gray-200 p-8 overflow-y-auto' + selectorContainer = document.createElement('div') + selectorContainer.className = 'mb-6 pb-6 border-b border-gray-200' + sel.className = 'w-full p-3 border border-gray-300 rounded-lg text-base mb-3' + gridContainer.className = + 'grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-6 flex-1 overflow-hidden' + addBtn = document.createElement('button') + stockInfo = document.createElement('div') + addBtn.id = 'add-to-cart' + stockInfo.id = 'stock-status' + stockInfo.className = 'text-xs text-red-500 mt-3 whitespace-pre-line' + addBtn.innerHTML = 'Add to Cart' + addBtn.className = + 'w-full py-3 bg-black text-white text-sm font-medium uppercase tracking-wider hover:bg-gray-800 transition-all' + selectorContainer.appendChild(sel) + selectorContainer.appendChild(addBtn) + selectorContainer.appendChild(stockInfo) + leftColumn.appendChild(selectorContainer) + cartDisp = document.createElement('div') + leftColumn.appendChild(cartDisp) + cartDisp.id = 'cart-items' + rightColumn = document.createElement('div') + rightColumn.className = 'bg-black text-white p-8 flex flex-col' rightColumn.innerHTML = `

Order Summary

@@ -102,29 +137,32 @@ function main() { Free shipping on all orders.
Earn loyalty points with purchase.

- `; - sum = rightColumn.querySelector('#cart-total'); - manualToggle = document.createElement('button'); + ` + sum = rightColumn.querySelector('#cart-total') + manualToggle = document.createElement('button') manualToggle.onclick = function () { - manualOverlay.classList.toggle('hidden'); - manualColumn.classList.toggle('translate-x-full'); - }; - manualToggle.className = 'fixed top-4 right-4 bg-black text-white p-3 rounded-full hover:bg-gray-900 transition-colors z-50'; + manualOverlay.classList.toggle('hidden') + manualColumn.classList.toggle('translate-x-full') + } + manualToggle.className = + 'fixed top-4 right-4 bg-black text-white p-3 rounded-full hover:bg-gray-900 transition-colors z-50' manualToggle.innerHTML = ` - `; - manualOverlay = document.createElement('div'); - manualOverlay.className = 'fixed inset-0 bg-black/50 z-40 hidden transition-opacity duration-300'; + ` + manualOverlay = document.createElement('div') + manualOverlay.className = + 'fixed inset-0 bg-black/50 z-40 hidden transition-opacity duration-300' manualOverlay.onclick = function (e) { if (e.target === manualOverlay) { - manualOverlay.classList.add('hidden'); - manualColumn.classList.add('translate-x-full'); + manualOverlay.classList.add('hidden') + manualColumn.classList.add('translate-x-full') } - }; - manualColumn = document.createElement('div'); - manualColumn.className = 'fixed right-0 top-0 h-full w-80 bg-white shadow-2xl p-6 overflow-y-auto z-50 transform translate-x-full transition-transform duration-300'; + } + manualColumn = document.createElement('div') + manualColumn.className = + 'fixed right-0 top-0 h-full w-80 bg-white shadow-2xl p-6 overflow-y-auto z-50 transform translate-x-full transition-transform duration-300' manualColumn.innerHTML = `
- `; - gridContainer.appendChild(leftColumn); - gridContainer.appendChild(rightColumn); - manualOverlay.appendChild(manualColumn); - root.appendChild(header); - root.appendChild(gridContainer); - root.appendChild(manualToggle); - root.appendChild(manualOverlay); - var initStock = 0; + ` + gridContainer.appendChild(leftColumn) + gridContainer.appendChild(rightColumn) + manualOverlay.appendChild(manualColumn) + root.appendChild(header) + root.appendChild(gridContainer) + root.appendChild(manualToggle) + root.appendChild(manualOverlay) + var initStock = 0 for (var i = 0; i < prodList.length; i++) { - initStock += prodList[i].q; + initStock += prodList[i].q } - onUpdateSelectOptions(); - handleCalculateCartStuff(); - lightningDelay = Math.random() * 10000; + onUpdateSelectOptions() + handleCalculateCartStuff() + lightningDelay = Math.random() * 10000 setTimeout(() => { setInterval(function () { - var luckyIdx = Math.floor(Math.random() * prodList.length); - var luckyItem = prodList[luckyIdx]; + var luckyIdx = Math.floor(Math.random() * prodList.length) + var luckyItem = prodList[luckyIdx] if (luckyItem.q > 0 && !luckyItem.onSale) { - - luckyItem.val = Math.round(luckyItem.originalVal * 80 / 100); - luckyItem.onSale = true; - alert('⚡번개세일! ' + luckyItem.name + '이(가) 20% 할인 중입니다!'); - onUpdateSelectOptions(); - doUpdatePricesInCart(); + luckyItem.val = Math.round((luckyItem.originalVal * 80) / 100) + luckyItem.onSale = true + alert('⚡번개세일! ' + luckyItem.name + '이(가) 20% 할인 중입니다!') + onUpdateSelectOptions() + doUpdatePricesInCart() } - }, 30000); - }, lightningDelay); + }, 30000) + }, lightningDelay) setTimeout(function () { setInterval(function () { if (cartDisp.children.length === 0) { } if (lastSel) { - var suggest = null; + var suggest = null for (var k = 0; k < prodList.length; k++) { if (prodList[k].id !== lastSel) { if (prodList[k].q > 0) { if (!prodList[k].suggestSale) { - suggest = prodList[k]; - break; + suggest = prodList[k] + break } } } } if (suggest) { - alert('💝 ' + suggest.name + '은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!'); + alert( + '💝 ' + + suggest.name + + '은(는) 어떠세요? 지금 구매하시면 5% 추가 할인!', + ) - suggest.val = Math.round(suggest.val * (100 - 5) / 100); - suggest.suggestSale = true; - onUpdateSelectOptions(); - doUpdatePricesInCart(); + suggest.val = Math.round((suggest.val * (100 - 5)) / 100) + suggest.suggestSale = true + onUpdateSelectOptions() + doUpdatePricesInCart() } } - }, 60000); - }, Math.random() * 20000); -}; + }, 60000) + }, Math.random() * 20000) +} var sum function onUpdateSelectOptions() { - var totalStock; - var opt; - var discountText; - sel.innerHTML = ''; - totalStock = 0; + var totalStock + var opt + var discountText + sel.innerHTML = '' + totalStock = 0 for (var idx = 0; idx < prodList.length; idx++) { - var _p = prodList[idx]; - totalStock = totalStock + _p.q; + var _p = prodList[idx] + totalStock = totalStock + _p.q } for (var i = 0; i < prodList.length; i++) { - (function() { - var item = prodList[i]; - opt = document.createElement("option") - opt.value = item.id; - discountText = ''; - if (item.onSale) discountText += ' ⚡SALE'; - if (item.suggestSale) discountText += ' 💝추천'; + ;(function () { + var item = prodList[i] + opt = document.createElement('option') + opt.value = item.id + discountText = '' + if (item.onSale) discountText += ' ⚡SALE' + if (item.suggestSale) discountText += ' 💝추천' if (item.q === 0) { - opt.textContent = item.name + ' - ' + item.val + '원 (품절)' + discountText + opt.textContent = + item.name + ' - ' + item.val + '원 (품절)' + discountText opt.disabled = true - opt.className = 'text-gray-400'; + opt.className = 'text-gray-400' } else { if (item.onSale && item.suggestSale) { - opt.textContent = '⚡💝' + item.name + ' - ' + item.originalVal + '원 → ' + item.val + '원 (25% SUPER SALE!)'; - opt.className = 'text-purple-600 font-bold'; + opt.textContent = + '⚡💝' + + item.name + + ' - ' + + item.originalVal + + '원 → ' + + item.val + + '원 (25% SUPER SALE!)' + opt.className = 'text-purple-600 font-bold' } else if (item.onSale) { - opt.textContent = '⚡' + item.name + ' - ' + item.originalVal + '원 → ' + item.val + '원 (20% SALE!)'; - opt.className = 'text-red-500 font-bold'; + opt.textContent = + '⚡' + + item.name + + ' - ' + + item.originalVal + + '원 → ' + + item.val + + '원 (20% SALE!)' + opt.className = 'text-red-500 font-bold' } else if (item.suggestSale) { - opt.textContent = '💝' + item.name + ' - ' + item.originalVal + '원 → ' + item.val + '원 (5% 추천할인!)'; - opt.className = 'text-blue-500 font-bold'; + opt.textContent = + '💝' + + item.name + + ' - ' + + item.originalVal + + '원 → ' + + item.val + + '원 (5% 추천할인!)' + opt.className = 'text-blue-500 font-bold' } else { - opt.textContent = item.name + ' - ' + item.val + '원' + discountText; + opt.textContent = item.name + ' - ' + item.val + '원' + discountText } } - sel.appendChild(opt); - })(); + sel.appendChild(opt) + })() } if (totalStock < 50) { - sel.style.borderColor = 'orange'; + sel.style.borderColor = 'orange' } else { - sel.style.borderColor = ''; + sel.style.borderColor = '' } } function handleCalculateCartStuff() { - var cartItems; - var subTot; - var itemDiscounts; - var lowStockItems; - var idx; - var originalTotal; - var bulkDisc; - var itemDisc; - var savedAmount; - var summaryDetails; - var totalDiv; - var loyaltyPointsDiv; - var points; - var discountInfoDiv; - var itemCountElement; - var previousCount; - var stockMsg; - var pts; - var hasP1; - var hasP2; - var loyaltyDiv; - totalAmt = 0; - itemCnt = 0; - cartItems = cartDisp.children; - subTot = 0; - itemDiscounts = []; - lowStockItems = []; + var cartItems + var subTot + var itemDiscounts + var lowStockItems + var idx + var originalTotal + var bulkDisc + var itemDisc + var savedAmount + var summaryDetails + var totalDiv + var loyaltyPointsDiv + var points + var discountInfoDiv + var itemCountElement + var previousCount + var stockMsg + var pts + var hasP1 + var hasP2 + var loyaltyDiv + totalAmt = 0 + itemCnt = 0 + cartItems = cartDisp.children + subTot = 0 + itemDiscounts = [] + lowStockItems = [] for (idx = 0; idx < prodList.length; idx++) { if (prodList[idx].q < 5 && prodList[idx].q > 0) { - lowStockItems.push(prodList[idx].name); + lowStockItems.push(prodList[idx].name) } } for (let i = 0; i < cartItems.length; i++) { - (function () { - var curItem; + ;(function () { + var curItem for (var j = 0; j < prodList.length; j++) { if (prodList[j].id === cartItems[i].id) { - curItem = prodList[j]; - break; + curItem = prodList[j] + break } } - var qtyElem = cartItems[i].querySelector('.quantity-number'); - var q; - var itemTot; - var disc; - q = parseInt(qtyElem.textContent); - itemTot = curItem.val * q; - disc = 0; - itemCnt += q; - subTot += itemTot; - var itemDiv = cartItems[i]; - var priceElems = itemDiv.querySelectorAll('.text-lg, .text-xs'); + var qtyElem = cartItems[i].querySelector('.quantity-number') + var q + var itemTot + var disc + q = parseInt(qtyElem.textContent) + itemTot = curItem.val * q + disc = 0 + itemCnt += q + subTot += itemTot + var itemDiv = cartItems[i] + var priceElems = itemDiv.querySelectorAll('.text-lg, .text-xs') priceElems.forEach(function (elem) { if (elem.classList.contains('text-lg')) { - elem.style.fontWeight = q >= 10 ? 'bold' : 'normal'; + elem.style.fontWeight = q >= 10 ? 'bold' : 'normal' } - }); + }) if (q >= 10) { if (curItem.id === PRODUCT_ONE) { - disc = 10 / 100; + disc = 10 / 100 } else { if (curItem.id === p2) { - disc = 15 / 100; + disc = 15 / 100 } else { if (curItem.id === product_3) { - disc = 20 / 100; + disc = 20 / 100 } else { if (curItem.id === p4) { - disc = 5 / 100; + disc = 5 / 100 } else { if (curItem.id === PRODUCT_5) { - disc = 25 / 100; + disc = 25 / 100 } } } } } if (disc > 0) { - itemDiscounts.push({name: curItem.name, discount: disc * 100}); + itemDiscounts.push({ name: curItem.name, discount: disc * 100 }) } } - totalAmt += itemTot * (1 - disc); - })(); + totalAmt += itemTot * (1 - disc) + })() } - let discRate = 0; - var originalTotal = subTot; + let discRate = 0 + var originalTotal = subTot if (itemCnt >= 30) { - totalAmt = subTot * 75 / 100; - discRate = 25 / 100; + totalAmt = (subTot * 75) / 100 + discRate = 25 / 100 } else { - discRate = (subTot - totalAmt) / subTot; + discRate = (subTot - totalAmt) / subTot } - const today = new Date(); - var isTuesday = today.getDay() === 2; - var tuesdaySpecial = document.getElementById('tuesday-special'); + const today = new Date() + var isTuesday = today.getDay() === 2 + var tuesdaySpecial = document.getElementById('tuesday-special') if (isTuesday) { if (totalAmt > 0) { - totalAmt = totalAmt * 90 / 100; + totalAmt = (totalAmt * 90) / 100 - discRate = 1 - (totalAmt / originalTotal); - tuesdaySpecial.classList.remove('hidden'); + discRate = 1 - totalAmt / originalTotal + tuesdaySpecial.classList.remove('hidden') } else { - tuesdaySpecial.classList.add('hidden'); + tuesdaySpecial.classList.add('hidden') } } else { - tuesdaySpecial.classList.add('hidden'); + tuesdaySpecial.classList.add('hidden') } - document.getElementById('item-count').textContent = '🛍️ ' + itemCnt + ' items in cart'; - summaryDetails = document.getElementById('summary-details'); - summaryDetails.innerHTML = ''; + document.getElementById('item-count').textContent = + '🛍️ ' + itemCnt + ' items in cart' + summaryDetails = document.getElementById('summary-details') + summaryDetails.innerHTML = '' if (subTot > 0) { - for (let i = 0; i < cartItems.length; i++) { - var curItem; + var curItem for (var j = 0; j < prodList.length; j++) { if (prodList[j].id === cartItems[i].id) { - curItem = prodList[j]; - break; + curItem = prodList[j] + break } } - var qtyElem = cartItems[i].querySelector('.quantity-number'); - var q = parseInt(qtyElem.textContent); - var itemTotal = curItem.val * q; + var qtyElem = cartItems[i].querySelector('.quantity-number') + var q = parseInt(qtyElem.textContent) + var itemTotal = curItem.val * q summaryDetails.innerHTML += `
${curItem.name} x ${q} ₩${itemTotal.toLocaleString()}
- `; + ` } summaryDetails.innerHTML += ` @@ -433,26 +496,24 @@ function handleCalculateCartStuff() { Subtotal ₩${subTot.toLocaleString()}
- `; + ` if (itemCnt >= 30) { - summaryDetails.innerHTML += `
🎉 대량구매 할인 (30개 이상) -25%
- `; + ` } else if (itemDiscounts.length > 0) { - itemDiscounts.forEach(function (item) { summaryDetails.innerHTML += `
${item.name} (10개↑) -${item.discount}%
- `; - }); + ` + }) } if (isTuesday) { if (totalAmt > 0) { @@ -461,7 +522,7 @@ function handleCalculateCartStuff() { 🌟 화요일 추가 할인 -10% - `; + ` } } summaryDetails.innerHTML += ` @@ -469,28 +530,28 @@ function handleCalculateCartStuff() { Shipping Free - `; + ` } - totalDiv = sum.querySelector('.text-2xl'); + totalDiv = sum.querySelector('.text-2xl') if (totalDiv) { - totalDiv.textContent = '₩' + Math.round(totalAmt).toLocaleString(); + totalDiv.textContent = '₩' + Math.round(totalAmt).toLocaleString() } - loyaltyPointsDiv = document.getElementById('loyalty-points'); + loyaltyPointsDiv = document.getElementById('loyalty-points') if (loyaltyPointsDiv) { - points = Math.floor(totalAmt / 1000); + points = Math.floor(totalAmt / 1000) if (points > 0) { - loyaltyPointsDiv.textContent = '적립 포인트: ' + points + 'p'; - loyaltyPointsDiv.style.display = 'block'; + loyaltyPointsDiv.textContent = '적립 포인트: ' + points + 'p' + loyaltyPointsDiv.style.display = 'block' } else { - loyaltyPointsDiv.textContent = '적립 포인트: 0p'; - loyaltyPointsDiv.style.display = 'block'; + loyaltyPointsDiv.textContent = '적립 포인트: 0p' + loyaltyPointsDiv.style.display = 'block' } } - discountInfoDiv = document.getElementById('discount-info'); - discountInfoDiv.innerHTML = ''; + discountInfoDiv = document.getElementById('discount-info') + discountInfoDiv.innerHTML = '' if (discRate > 0 && totalAmt > 0) { - savedAmount = originalTotal - totalAmt; + savedAmount = originalTotal - totalAmt discountInfoDiv.innerHTML = `
@@ -499,227 +560,252 @@ function handleCalculateCartStuff() {
₩${Math.round(savedAmount).toLocaleString()} 할인되었습니다
- `; + ` } - itemCountElement = document.getElementById('item-count'); + itemCountElement = document.getElementById('item-count') if (itemCountElement) { - previousCount = parseInt(itemCountElement.textContent.match(/\d+/) || 0); - itemCountElement.textContent = '🛍️ ' + itemCnt + ' items in cart'; + previousCount = parseInt(itemCountElement.textContent.match(/\d+/) || 0) + itemCountElement.textContent = '🛍️ ' + itemCnt + ' items in cart' if (previousCount !== itemCnt) { - itemCountElement.setAttribute('data-changed', 'true'); + itemCountElement.setAttribute('data-changed', 'true') } } - stockMsg = ''; + stockMsg = '' for (var stockIdx = 0; stockIdx < prodList.length; stockIdx++) { - var item = prodList[stockIdx]; + var item = prodList[stockIdx] if (item.q < 5) { if (item.q > 0) { - stockMsg = stockMsg + item.name + ': 재고 부족 (' + item.q + '개 남음)\n'; + stockMsg = + stockMsg + item.name + ': 재고 부족 (' + item.q + '개 남음)\n' } else { - stockMsg = stockMsg + item.name + ': 품절\n'; + stockMsg = stockMsg + item.name + ': 품절\n' } } } - stockInfo.textContent = stockMsg; + stockInfo.textContent = stockMsg - handleStockInfoUpdate(); - doRenderBonusPoints(); + handleStockInfoUpdate() + doRenderBonusPoints() } -var doRenderBonusPoints = function() { - var basePoints; - var finalPoints; - var pointsDetail; +var doRenderBonusPoints = function () { + var basePoints + var finalPoints + var pointsDetail - var hasKeyboard; - var hasMouse; - var hasMonitorArm; - var nodes; + var hasKeyboard + var hasMouse + var hasMonitorArm + var nodes if (cartDisp.children.length === 0) { - document.getElementById('loyalty-points').style.display = 'none'; - return; + document.getElementById('loyalty-points').style.display = 'none' + return } basePoints = Math.floor(totalAmt / 1000) - finalPoints = 0; - pointsDetail = []; + finalPoints = 0 + pointsDetail = [] if (basePoints > 0) { - finalPoints = basePoints; - pointsDetail.push('기본: ' + basePoints + 'p'); + finalPoints = basePoints + pointsDetail.push('기본: ' + basePoints + 'p') } if (new Date().getDay() === 2) { if (basePoints > 0) { - finalPoints = basePoints * 2; - pointsDetail.push('화요일 2배'); + finalPoints = basePoints * 2 + pointsDetail.push('화요일 2배') } } - hasKeyboard = false; - hasMouse = false; - hasMonitorArm = false; - nodes = cartDisp.children; + hasKeyboard = false + hasMouse = false + hasMonitorArm = false + nodes = cartDisp.children for (const node of nodes) { - var product = null; + var product = null for (var pIdx = 0; pIdx < prodList.length; pIdx++) { if (prodList[pIdx].id === node.id) { - product = prodList[pIdx]; - break; + product = prodList[pIdx] + break } } - if (!product) continue; + if (!product) continue if (product.id === PRODUCT_ONE) { - hasKeyboard = true; + hasKeyboard = true } else if (product.id === p2) { - hasMouse = true; + hasMouse = true } else if (product.id === product_3) { - hasMonitorArm = true; + hasMonitorArm = true } } if (hasKeyboard && hasMouse) { - finalPoints = finalPoints + 50; - pointsDetail.push('키보드+마우스 세트 +50p'); + finalPoints = finalPoints + 50 + pointsDetail.push('키보드+마우스 세트 +50p') } if (hasKeyboard && hasMouse && hasMonitorArm) { - finalPoints = finalPoints + 100; - pointsDetail.push('풀세트 구매 +100p'); + finalPoints = finalPoints + 100 + pointsDetail.push('풀세트 구매 +100p') } if (itemCnt >= 30) { - finalPoints = finalPoints + 100; - pointsDetail.push('대량구매(30개+) +100p'); + finalPoints = finalPoints + 100 + pointsDetail.push('대량구매(30개+) +100p') } else { if (itemCnt >= 20) { - finalPoints = finalPoints + 50; - pointsDetail.push('대량구매(20개+) +50p'); + finalPoints = finalPoints + 50 + pointsDetail.push('대량구매(20개+) +50p') } else { if (itemCnt >= 10) { - finalPoints = finalPoints + 20; - pointsDetail.push('대량구매(10개+) +20p'); + finalPoints = finalPoints + 20 + pointsDetail.push('대량구매(10개+) +20p') } } } - bonusPts = finalPoints; - var ptsTag = document.getElementById('loyalty-points'); + bonusPts = finalPoints + var ptsTag = document.getElementById('loyalty-points') if (ptsTag) { if (bonusPts > 0) { - ptsTag.innerHTML = '
적립 포인트: ' + bonusPts + 'p
' + - '
' + pointsDetail.join(', ') + '
'; - ptsTag.style.display = 'block'; + ptsTag.innerHTML = + '
적립 포인트: ' + + bonusPts + + 'p
' + + '
' + + pointsDetail.join(', ') + + '
' + ptsTag.style.display = 'block' } else { - ptsTag.textContent = '적립 포인트: 0p'; + ptsTag.textContent = '적립 포인트: 0p' ptsTag.style.display = 'block' } } } function onGetStockTotal() { - var sum; - var i; - var currentProduct; - sum = 0; + var sum + var i + var currentProduct + sum = 0 for (i = 0; i < prodList.length; i++) { - currentProduct = prodList[i]; - sum += currentProduct.q; + currentProduct = prodList[i] + sum += currentProduct.q } - return sum; + return sum } -var handleStockInfoUpdate = function() { - var infoMsg; - var totalStock; - var messageOptimizer; - infoMsg = ''; - totalStock = onGetStockTotal(); +var handleStockInfoUpdate = function () { + var infoMsg + var totalStock + var messageOptimizer + infoMsg = '' + totalStock = onGetStockTotal() if (totalStock < 30) { } prodList.forEach(function (item) { if (item.q < 5) { if (item.q > 0) { - infoMsg = infoMsg + item.name + ': 재고 부족 (' + item.q + '개 남음)\n'; + infoMsg = infoMsg + item.name + ': 재고 부족 (' + item.q + '개 남음)\n' } else { - infoMsg = infoMsg + item.name + ': 품절\n'; + infoMsg = infoMsg + item.name + ': 품절\n' } } - }); - stockInfo.textContent = infoMsg; + }) + stockInfo.textContent = infoMsg } function doUpdatePricesInCart() { - var totalCount = 0, j = 0; - var cartItems; + var totalCount = 0, + j = 0 + var cartItems while (cartDisp.children[j]) { - var qty = cartDisp.children[j].querySelector('.quantity-number'); - totalCount += qty ? parseInt(qty.textContent) : 0; - j++; + var qty = cartDisp.children[j].querySelector('.quantity-number') + totalCount += qty ? parseInt(qty.textContent) : 0 + j++ } - totalCount = 0; + totalCount = 0 for (j = 0; j < cartDisp.children.length; j++) { - totalCount += parseInt(cartDisp.children[j].querySelector('.quantity-number').textContent); + totalCount += parseInt( + cartDisp.children[j].querySelector('.quantity-number').textContent, + ) } - cartItems = cartDisp.children; + cartItems = cartDisp.children for (var i = 0; i < cartItems.length; i++) { - var itemId = cartItems[i].id; - var product = null; + var itemId = cartItems[i].id + var product = null for (var productIdx = 0; productIdx < prodList.length; productIdx++) { if (prodList[productIdx].id === itemId) { - product = prodList[productIdx]; - break; + product = prodList[productIdx] + break } } if (product) { - var priceDiv = cartItems[i].querySelector('.text-lg'); - var nameDiv = cartItems[i].querySelector('h3'); + var priceDiv = cartItems[i].querySelector('.text-lg') + var nameDiv = cartItems[i].querySelector('h3') if (product.onSale && product.suggestSale) { - priceDiv.innerHTML = '₩' + product.originalVal.toLocaleString() + ' ₩' + product.val.toLocaleString() + ''; - nameDiv.textContent = '⚡💝' + product.name; + priceDiv.innerHTML = + '₩' + + product.originalVal.toLocaleString() + + ' ₩' + + product.val.toLocaleString() + + '' + nameDiv.textContent = '⚡💝' + product.name } else if (product.onSale) { - priceDiv.innerHTML = '₩' + product.originalVal.toLocaleString() + ' ₩' + product.val.toLocaleString() + ''; - nameDiv.textContent = '⚡' + product.name; + priceDiv.innerHTML = + '₩' + + product.originalVal.toLocaleString() + + ' ₩' + + product.val.toLocaleString() + + '' + nameDiv.textContent = '⚡' + product.name } else if (product.suggestSale) { - priceDiv.innerHTML = '₩' + product.originalVal.toLocaleString() + ' ₩' + product.val.toLocaleString() + ''; - nameDiv.textContent = '💝' + product.name; + priceDiv.innerHTML = + '₩' + + product.originalVal.toLocaleString() + + ' ₩' + + product.val.toLocaleString() + + '' + nameDiv.textContent = '💝' + product.name } else { - priceDiv.textContent = '₩' + product.val.toLocaleString(); - nameDiv.textContent = product.name; + priceDiv.textContent = '₩' + product.val.toLocaleString() + nameDiv.textContent = product.name } } } - handleCalculateCartStuff(); + handleCalculateCartStuff() } -main(); -addBtn.addEventListener("click", function () { +main() +addBtn.addEventListener('click', function () { var selItem = sel.value - var hasItem = false; + var hasItem = false for (var idx = 0; idx < prodList.length; idx++) { if (prodList[idx].id === selItem) { - hasItem = true; - break; + hasItem = true + break } } if (!selItem || !hasItem) { - return; + return } - var itemToAdd = null; + var itemToAdd = null for (var j = 0; j < prodList.length; j++) { if (prodList[j].id === selItem) { - itemToAdd = prodList[j]; - break; + itemToAdd = prodList[j] + break } } if (itemToAdd && itemToAdd.q > 0) { - var item = document.getElementById(itemToAdd['id']); + var item = document.getElementById(itemToAdd['id']) if (item) { var qtyElem = item.querySelector('.quantity-number') var newQty = parseInt(qtyElem['textContent']) + 1 if (newQty <= itemToAdd.q + parseInt(qtyElem.textContent)) { - qtyElem.textContent = newQty; + qtyElem.textContent = newQty itemToAdd['q']-- } else { - alert('재고가 부족합니다.'); + alert('재고가 부족합니다.') } } else { - var newItem = document.createElement('div'); - newItem.id = itemToAdd.id; - newItem.className = 'grid grid-cols-[80px_1fr_auto] gap-5 py-5 border-b border-gray-100 first:pt-0 last:border-b-0 last:pb-0'; + var newItem = document.createElement('div') + newItem.id = itemToAdd.id + newItem.className = + 'grid grid-cols-[80px_1fr_auto] gap-5 py-5 border-b border-gray-100 first:pt-0 last:border-b-0 last:pb-0' newItem.innerHTML = `
@@ -738,50 +824,55 @@ addBtn.addEventListener("click", function () {
${itemToAdd.onSale || itemToAdd.suggestSale ? '₩' + itemToAdd.originalVal.toLocaleString() + ' ₩' + itemToAdd.val.toLocaleString() + '' : '₩' + itemToAdd.val.toLocaleString()}
Remove
- `; - cartDisp.appendChild(newItem); - itemToAdd.q--; + ` + cartDisp.appendChild(newItem) + itemToAdd.q-- } - handleCalculateCartStuff(); - lastSel = selItem; - } -}); -cartDisp.addEventListener("click", function (event) { - var tgt = event.target; - if (tgt.classList.contains('quantity-change') || tgt.classList.contains("remove-item")) { - var prodId = tgt.dataset.productId; + handleCalculateCartStuff() + lastSel = selItem + } +}) +cartDisp.addEventListener('click', function (event) { + var tgt = event.target + if ( + tgt.classList.contains('quantity-change') || + tgt.classList.contains('remove-item') + ) { + var prodId = tgt.dataset.productId var itemElem = document.getElementById(prodId) - var prod = null; + var prod = null for (var prdIdx = 0; prdIdx < prodList.length; prdIdx++) { if (prodList[prdIdx].id === prodId) { - prod = prodList[prdIdx]; - break; + prod = prodList[prdIdx] + break } } if (tgt.classList.contains('quantity-change')) { - var qtyChange = parseInt(tgt.dataset.change); - var qtyElem = itemElem.querySelector('.quantity-number'); - var currentQty = parseInt(qtyElem.textContent); - var newQty = currentQty + qtyChange; + var qtyChange = parseInt(tgt.dataset.change) + var qtyElem = itemElem.querySelector('.quantity-number') + var currentQty = parseInt(qtyElem.textContent) + var newQty = currentQty + qtyChange if (newQty > 0 && newQty <= prod.q + currentQty) { - qtyElem.textContent = newQty; - prod.q -= qtyChange; + qtyElem.textContent = newQty + prod.q -= qtyChange } else if (newQty <= 0) { - prod.q += currentQty; - itemElem.remove(); + prod.q += currentQty + itemElem.remove() } else { - alert('재고가 부족합니다.'); + alert('재고가 부족합니다.') } } else if (tgt.classList.contains('remove-item')) { - var qtyElem = itemElem.querySelector('.quantity-number'); - var remQty = parseInt(qtyElem.textContent); - prod.q += remQty; - itemElem.remove(); + var qtyElem = itemElem.querySelector('.quantity-number') + var remQty = parseInt(qtyElem.textContent) + prod.q += remQty + itemElem.remove() } if (prod && prod.q < 5) { } - handleCalculateCartStuff(); - onUpdateSelectOptions(); + handleCalculateCartStuff() + onUpdateSelectOptions() } -}); \ No newline at end of file +}) + +export {}