commit d5d4883917c304a3618ddee36c64b7b349b70a22 Author: Rafał Miczek Date: Fri Feb 13 19:58:25 2026 +0100 Docker, API proxy, Drone CI, .env Co-authored-by: Cursor diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a05af74 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +node_modules +dist +.git +.env +.env.* +*.md +coverage +.angular +out-tsc +*.log diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..90239dc --- /dev/null +++ b/.drone.yml @@ -0,0 +1,37 @@ +kind: pipeline +type: docker +name: build-and-push + +steps: + - name: test + image: oven/bun:1-alpine + commands: + - bun install --frozen-lockfile + - bun run test + + - name: build + image: oven/bun:1-alpine + commands: + - bun install --frozen-lockfile + - bun run build:ssr + depends_on: + - test + + - name: docker + image: plugins/docker + settings: + repo: registry.zea.lt/miczek/tracker + registry: registry.zea.lt + username: + from_secret: registry_username + password: + from_secret: registry_password + tags: + - latest + - ${DRONE_COMMIT_SHA:0:7} + when: + branch: + - main + - master + depends_on: + - build diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6635169 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,32 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false + + + + + + + + + + + + + + + + diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ad2f00b --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# Strava OAuth +STRAVA_CLIENT_ID= +STRAVA_CLIENT_SECRET= +STRAVA_REDIRECT_URI= + +# Riot Games API +RIOT_API_KEY_LOL= +RIOT_API_KEY_TFT= +RIOT_REGION= +RIOT_SUMMONER_NAME_LOL= +RIOT_SUMMONER_NAME_TFT= +RIOT_TAG_LINE= + +# Faceit API +FACEIT_API_KEY= +FACEIT_USER_ID= + +# Tracker.gg API +TRACKER_GG_API_KEY= +ROCKET_LEAGUE_PLATFORM= +ROCKET_LEAGUE_USERNAME= + +# Server +PORT=4000 diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..892721a --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,60 @@ +{ + "root": true, + "ignorePatterns": ["projects/**/*"], + "overrides": [ + { + "files": ["*.ts"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@angular-eslint/recommended", + "plugin:@angular-eslint/template/process-inline-templates" + ], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "app", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "app", + "style": "kebab-case" + } + ], + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/explicit-function-return-type": "off", + "max-len": ["error", { "code": 80 }] + } + }, + { + "files": ["*.html"], + "extends": [ + "plugin:@angular-eslint/template/recommended", + "plugin:@angular-eslint/template/accessibility" + ], + "rules": {} + } + ] +} + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd6a21a --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db + +# Environment and secrets +.env +.env.local +.env.*.local + + + + + + + + + + + + + + + + diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..666ad0d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,23 @@ +{ + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "printWidth": 80 +} + + + + + + + + + + + + + + + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a3441f2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +# Stage 1: Build Angular and server +FROM oven/bun:1-alpine AS builder +WORKDIR /app + +COPY package*.json bun.lock* ./ +RUN bun install --frozen-lockfile + +COPY . . +# Non-secret env for build; pass build args if needed +ARG STRAVA_CLIENT_ID= +ARG STRAVA_REDIRECT_URI= +ARG RIOT_REGION=eun1 +ARG RIOT_SUMMONER_NAME_LOL= +ARG RIOT_SUMMONER_NAME_TFT= +ARG RIOT_TAG_LINE= +ARG FACEIT_USER_ID= +ARG ROCKET_LEAGUE_PLATFORM=steam +ARG ROCKET_LEAGUE_USERNAME= + +ENV STRAVA_CLIENT_ID=$STRAVA_CLIENT_ID \ + STRAVA_REDIRECT_URI=$STRAVA_REDIRECT_URI \ + RIOT_REGION=$RIOT_REGION \ + RIOT_SUMMONER_NAME_LOL=$RIOT_SUMMONER_NAME_LOL \ + RIOT_SUMMONER_NAME_TFT=$RIOT_SUMMONER_NAME_TFT \ + RIOT_TAG_LINE=$RIOT_TAG_LINE \ + FACEIT_USER_ID=$FACEIT_USER_ID \ + ROCKET_LEAGUE_PLATFORM=$ROCKET_LEAGUE_PLATFORM \ + ROCKET_LEAGUE_USERNAME=$ROCKET_LEAGUE_USERNAME + +RUN bun run build:ssr + +# Stage 2: Runtime +FROM oven/bun:1-alpine +WORKDIR /app + +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/scripts ./scripts +COPY --from=builder /app/package*.json bun.lock* ./ +RUN bun install --frozen-lockfile --production + +EXPOSE 4000 +ENV NODE_ENV=production +CMD ["node", "scripts/serve-ssr.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..63ecca6 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# 2026 Goals Tracker + +An Angular Universal application to track your 2026 sport and gaming goals. + +## Features + +- **Sport Goals**: Track biking (7,500km), running (2,500km), and swimming (250km) via Strava API +- **Gaming Goals**: Monitor progress toward Diamond rank in TFT/LoL, Champion in Rocket League, and Level 10 in Faceit +- **Progress Visualization**: Charts and progress bars showing your progress over time +- **On-Track Analysis**: See if you're on track to meet your goals by the end of 2026 + +## Setup + +1. Install dependencies: +```bash +npm install +``` + +2. Configure environment variables in `src/environments/environment.ts`: + - Strava: Client ID, Client Secret, Redirect URI + - Riot Games: API Key, Region, Summoner Names + - Faceit: API Key, User ID + - Tracker.gg: API Key, Rocket League Platform/Username + +3. Run development server: +```bash +npm start +``` + +4. Build for production: +```bash +npm run build +``` + +5. Run SSR server: +```bash +npm run serve:ssr +``` + +## API Setup + +### Strava +1. Go to https://www.strava.com/settings/api +2. Create a new application +3. Set redirect URI to `http://localhost:4200/auth/strava/callback` +4. Copy Client ID and Client Secret + +### Riot Games +1. Go to https://developer.riotgames.com/ +2. Create an account and get an API key +3. Note your region (e.g., `euw1`, `na1`) +4. Provide your summoner names for LoL and TFT + +### Faceit +1. Go to https://developers.faceit.com/ +2. Create an API key +3. Provide your Faceit user ID + +### Tracker.gg +1. Check https://tracker.gg/developers/docs for API access +2. Get API key if available +3. Provide your Rocket League platform and username + +## Project Structure + +- `src/app/core/` - Services, models, and interceptors +- `src/app/features/` - Feature components (dashboard, sport, gaming) +- `src/app/shared/` - Shared components and utilities +- `src/environments/` - Environment configuration + +## Technologies + +- Angular 19 with Universal SSR +- Tailwind CSS for styling +- Chart.js with ng2-charts for visualizations +- RxJS for reactive programming +- TypeScript with strict mode + + + + + + + + + + + + + + + + diff --git a/angular.json b/angular.json new file mode 100644 index 0000000..7961fd6 --- /dev/null +++ b/angular.json @@ -0,0 +1,107 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "goals-tracker": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss", + "standalone": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/goals-tracker", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "tsconfig.app.json", + "assets": ["src/favicon.ico", "src/assets"], + "styles": ["src/styles.scss"], + "scripts": [], + "stylePreprocessorOptions": { + "includePaths": ["src/styles"] + } + }, + "configurations": { +"production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kB", + "maximumError": "10kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "goals-tracker:build:production" + }, + "development": { + "buildTarget": "goals-tracker:build:development" + } + }, + "defaultConfiguration": "development" + }, + "server": { + "builder": "@angular-devkit/build-angular:server", + "options": { + "outputPath": "dist/goals-tracker/server", + "main": "server.ts", + "tsConfig": "tsconfig.server.json" + }, + "configurations": { + "production": { + "outputHashing": "all" + }, + "development": { + "optimization": false, + "sourceMap": true, + "extractLicenses": false + } + }, + "defaultConfiguration": "production" + }, + "prerender": { + "builder": "@angular-devkit/build-angular:prerender", + "options": { + "routes": ["/"] + } + } + } + } + }, + "cli": { + "analytics": false + } +} + diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..8c72526 --- /dev/null +++ b/bun.lock @@ -0,0 +1,2471 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "goals-tracker", + "dependencies": { + "@angular/animations": "^19.0.0", + "@angular/common": "^19.0.0", + "@angular/compiler": "^19.2.18", + "@angular/compiler-cli": "^19.2.18", + "@angular/core": "^19.2.18", + "@angular/platform-browser": "^19.0.0", + "@angular/platform-browser-dynamic": "^19.0.0", + "@angular/platform-server": "^19.0.0", + "@angular/router": "^19.0.0", + "@angular/ssr": "^19.0.0", + "@types/three": "^0.182.0", + "chart.js": "^4.4.0", + "chartjs-plugin-datalabels": "^2.2.0", + "chartjs-plugin-zoom": "^2.2.0", + "dotenv": "^16.4.5", + "express": "^4.18.2", + "ng2-charts": "^5.0.0", + "rxjs": "~7.8.0", + "three": "^0.182.0", + "tslib": "^2.3.0", + "zone.js": "~0.14.3", + }, + "devDependencies": { + "@angular-devkit/build-angular": "^19.2.19", + "@angular-eslint/builder": "^19.0.0", + "@angular-eslint/eslint-plugin": "^19.0.0", + "@angular-eslint/eslint-plugin-template": "^19.0.0", + "@angular-eslint/schematics": "^19.0.0", + "@angular-eslint/template-parser": "^19.0.0", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.0", + "@types/node": "^20.10.0", + "autoprefixer": "^10.4.16", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "jest-preset-angular": "^13.1.0", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0", + "typescript": "~5.8.0", + }, + }, + }, + "packages": { + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@angular-devkit/architect": ["@angular-devkit/architect@0.1902.19", "", { "dependencies": { "@angular-devkit/core": "19.2.19", "rxjs": "7.8.1" } }, "sha512-iexYDIYpGAeAU7T60bGcfrGwtq1bxpZixYxWuHYiaD1b5baQgNSfd1isGEOh37GgDNsf4In9i2LOLPm0wBdtgQ=="], + + "@angular-devkit/build-angular": ["@angular-devkit/build-angular@19.2.19", "", { "dependencies": { "@ampproject/remapping": "2.3.0", "@angular-devkit/architect": "0.1902.19", "@angular-devkit/build-webpack": "0.1902.19", "@angular-devkit/core": "19.2.19", "@angular/build": "19.2.19", "@babel/core": "7.26.10", "@babel/generator": "7.26.10", "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", "@babel/plugin-transform-async-generator-functions": "7.26.8", "@babel/plugin-transform-async-to-generator": "7.25.9", "@babel/plugin-transform-runtime": "7.26.10", "@babel/preset-env": "7.26.9", "@babel/runtime": "7.26.10", "@discoveryjs/json-ext": "0.6.3", "@ngtools/webpack": "19.2.19", "@vitejs/plugin-basic-ssl": "1.2.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", "babel-loader": "9.2.1", "browserslist": "^4.21.5", "copy-webpack-plugin": "12.0.2", "css-loader": "7.1.2", "esbuild-wasm": "0.25.4", "fast-glob": "3.3.3", "http-proxy-middleware": "3.0.5", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", "karma-source-map-support": "1.4.0", "less": "4.2.2", "less-loader": "12.2.0", "license-webpack-plugin": "4.0.2", "loader-utils": "3.3.1", "mini-css-extract-plugin": "2.9.2", "open": "10.1.0", "ora": "5.4.1", "picomatch": "4.0.2", "piscina": "4.8.0", "postcss": "8.5.2", "postcss-loader": "8.1.1", "resolve-url-loader": "5.0.0", "rxjs": "7.8.1", "sass": "1.85.0", "sass-loader": "16.0.5", "semver": "7.7.1", "source-map-loader": "5.0.0", "source-map-support": "0.5.21", "terser": "5.39.0", "tree-kill": "1.2.2", "tslib": "2.8.1", "webpack": "5.98.0", "webpack-dev-middleware": "7.4.2", "webpack-dev-server": "5.2.2", "webpack-merge": "6.0.1", "webpack-subresource-integrity": "5.1.0" }, "optionalDependencies": { "esbuild": "0.25.4" }, "peerDependencies": { "@angular/compiler-cli": "^19.0.0 || ^19.2.0-next.0", "@angular/localize": "^19.0.0 || ^19.2.0-next.0", "@angular/platform-server": "^19.0.0 || ^19.2.0-next.0", "@angular/service-worker": "^19.0.0 || ^19.2.0-next.0", "@angular/ssr": "^19.2.19", "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", "jest-environment-jsdom": "^29.5.0", "karma": "^6.3.0", "ng-packagr": "^19.0.0 || ^19.2.0-next.0", "protractor": "^7.0.0", "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", "typescript": ">=5.5 <5.9" }, "optionalPeers": ["@angular/localize", "@angular/platform-server", "@angular/service-worker", "@angular/ssr", "@web/test-runner", "browser-sync", "jest", "jest-environment-jsdom", "karma", "ng-packagr", "protractor", "tailwindcss"] }, "sha512-uIxi6Vzss6+ycljVhkyPUPWa20w8qxJL9lEn0h6+sX/fhM8Djt0FHIuTQjoX58EoMaQ/1jrXaRaGimkbaFcG9A=="], + + "@angular-devkit/build-webpack": ["@angular-devkit/build-webpack@0.1902.19", "", { "dependencies": { "@angular-devkit/architect": "0.1902.19", "rxjs": "7.8.1" }, "peerDependencies": { "webpack": "^5.30.0", "webpack-dev-server": "^5.0.2" } }, "sha512-x2tlGg5CsUveFzuRuqeHknSbGirSAoRynEh+KqPRGK0G3WpMViW/M8SuVurecasegfIrDWtYZ4FnVxKqNbKwXQ=="], + + "@angular-devkit/core": ["@angular-devkit/core@19.2.19", "", { "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.2", "rxjs": "7.8.1", "source-map": "0.7.4" }, "peerDependencies": { "chokidar": "^4.0.0" }, "optionalPeers": ["chokidar"] }, "sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ=="], + + "@angular-devkit/schematics": ["@angular-devkit/schematics@19.2.19", "", { "dependencies": { "@angular-devkit/core": "19.2.19", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "5.4.1", "rxjs": "7.8.1" } }, "sha512-J4Jarr0SohdrHcb40gTL4wGPCQ952IMWF1G/MSAQfBAPvA9ZKApYhpxcY7PmehVePve+ujpus1dGsJ7dPxz8Kg=="], + + "@angular-eslint/builder": ["@angular-eslint/builder@19.8.1", "", { "dependencies": { "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0", "@angular-devkit/core": ">= 19.0.0 < 20.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-NOMkw0xgDoDVCLkL5nkkvdd3ouDYkOGqtEmabTR7N4/kQnk1R4coOTWGCqAgMXCFdxlyjuxquDwuJ+yni81pRg=="], + + "@angular-eslint/bundled-angular-compiler": ["@angular-eslint/bundled-angular-compiler@19.8.1", "", {}, "sha512-WXi1YbSs7SIQo48u+fCcc5Nt14/T4QzYQPLZUnjtsUXPgQG7ZoahhcGf7PPQ+n0V3pSopHOlSHwqK+tSsYK87A=="], + + "@angular-eslint/eslint-plugin": ["@angular-eslint/eslint-plugin@19.8.1", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.8.1", "@angular-eslint/utils": "19.8.1" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-wZEBMPwD2TRhifG751hcj137EMIEaFmsxRB2EI+vfINCgPnFGSGGOHXqi8aInn9fXqHs7VbXkAzXYdBsvy1m4Q=="], + + "@angular-eslint/eslint-plugin-template": ["@angular-eslint/eslint-plugin-template@19.8.1", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.8.1", "@angular-eslint/utils": "19.8.1", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, "peerDependencies": { "@angular-eslint/template-parser": "19.8.1", "@typescript-eslint/types": "^7.11.0 || ^8.0.0", "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-0ZVQldndLrDfB0tzFe/uIwvkUcakw8qGxvkEU0l7kSbv/ngNQ/qrkRi7P64otB15inIDUNZI2jtmVat52dqSfQ=="], + + "@angular-eslint/schematics": ["@angular-eslint/schematics@19.8.1", "", { "dependencies": { "@angular-devkit/core": ">= 19.0.0 < 20.0.0", "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", "@angular-eslint/eslint-plugin": "19.8.1", "@angular-eslint/eslint-plugin-template": "19.8.1", "ignore": "7.0.5", "semver": "7.7.2", "strip-json-comments": "3.1.1" } }, "sha512-MKzfO3puOCuQFgP8XDUkEr5eaqcCQLAdYLLMcywEO/iRs1eRHL46+rkW+SjDp1cUqlxKtu+rLiTYr0T/O4fi9Q=="], + + "@angular-eslint/template-parser": ["@angular-eslint/template-parser@19.8.1", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.8.1", "eslint-scope": "^8.0.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-pQiOg+se1AU/ncMlnJ9V6xYnMQ84qI1BGWuJpbU6A99VTXJg90scg0+T7DWmKssR1YjP5qmmBtrZfKsHEcLW/A=="], + + "@angular-eslint/utils": ["@angular-eslint/utils@19.8.1", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.8.1" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-gVDKYWmAjeTPtaYmddT/HS03fCebXJtrk8G1MouQIviZbHqLjap6TbVlzlkBigRzaF0WnFnrDduQslkJzEdceA=="], + + "@angular/animations": ["@angular/animations@19.2.18", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": "19.2.18", "@angular/core": "19.2.18" } }, "sha512-c76x1t+OiSstPsvJdHmV8Q4taF+8SxWKqiY750fOjpd01it4jJbU6YQqIroC6Xie7154zZIxOTHH2uTj+nm5qA=="], + + "@angular/build": ["@angular/build@19.2.19", "", { "dependencies": { "@ampproject/remapping": "2.3.0", "@angular-devkit/architect": "0.1902.19", "@babel/core": "7.26.10", "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", "@babel/plugin-syntax-import-attributes": "7.26.0", "@inquirer/confirm": "5.1.6", "@vitejs/plugin-basic-ssl": "1.2.0", "beasties": "0.3.2", "browserslist": "^4.23.0", "esbuild": "0.25.4", "fast-glob": "3.3.3", "https-proxy-agent": "7.0.6", "istanbul-lib-instrument": "6.0.3", "listr2": "8.2.5", "magic-string": "0.30.17", "mrmime": "2.0.1", "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", "piscina": "4.8.0", "rollup": "4.34.8", "sass": "1.85.0", "semver": "7.7.1", "source-map-support": "0.5.21", "vite": "6.4.1", "watchpack": "2.4.2" }, "optionalDependencies": { "lmdb": "3.2.6" }, "peerDependencies": { "@angular/compiler": "^19.0.0 || ^19.2.0-next.0", "@angular/compiler-cli": "^19.0.0 || ^19.2.0-next.0", "@angular/localize": "^19.0.0 || ^19.2.0-next.0", "@angular/platform-server": "^19.0.0 || ^19.2.0-next.0", "@angular/service-worker": "^19.0.0 || ^19.2.0-next.0", "@angular/ssr": "^19.2.19", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^19.0.0 || ^19.2.0-next.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", "typescript": ">=5.5 <5.9" }, "optionalPeers": ["@angular/localize", "@angular/platform-server", "@angular/service-worker", "@angular/ssr", "karma", "less", "ng-packagr", "postcss", "tailwindcss"] }, "sha512-SFzQ1bRkNFiOVu+aaz+9INmts7tDUrsHLEr9HmARXr9qk5UmR8prlw39p2u+Bvi6/lCiJ18TZMQQl9mGyr63lg=="], + + "@angular/cdk": ["@angular/cdk@21.1.0", "", { "dependencies": { "parse5": "^8.0.0", "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": "^21.0.0 || ^22.0.0", "@angular/core": "^21.0.0 || ^22.0.0", "@angular/platform-browser": "^21.0.0 || ^22.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-zvV37HPKhtu0bOfuK0IhjKKq++Xb57Z11uZYZJI34BZnZ5y1TPhJpcmrHhjb2uKUNfDvePUqhlnIlKAXHSBIhw=="], + + "@angular/common": ["@angular/common@19.2.18", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/core": "19.2.18", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-CrV02Omzw/QtfjlEVXVPJVXipdx83NuA+qSASZYrxrhKFusUZyK3P/Zznqg+wiAeNDbedQwMUVqoAARHf0xQrw=="], + + "@angular/compiler": ["@angular/compiler@19.2.18", "", { "dependencies": { "tslib": "^2.3.0" } }, "sha512-3MscvODxRVxc3Cs0ZlHI5Pk5rEvE80otfvxZTMksOZuPlv1B+S8MjWfc3X3jk9SbyUEzODBEH55iCaBHD48V3g=="], + + "@angular/compiler-cli": ["@angular/compiler-cli@19.2.18", "", { "dependencies": { "@babel/core": "7.26.9", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^4.0.0", "convert-source-map": "^1.5.1", "reflect-metadata": "^0.2.0", "semver": "^7.0.0", "tslib": "^2.3.0", "yargs": "^17.2.1" }, "peerDependencies": { "@angular/compiler": "19.2.18", "typescript": ">=5.5 <5.9" }, "bin": { "ngc": "bundles/src/bin/ngc.js", "ngcc": "bundles/ngcc/index.js", "ng-xi18n": "bundles/src/bin/ng_xi18n.js" } }, "sha512-N4TMtLfImJIoMaRL6mx7885UBeQidywptHH6ACZj71Ar6++DBc1mMlcwuvbeJCd3r3y8MQ5nLv5PZSN/tHr13w=="], + + "@angular/core": ["@angular/core@19.2.18", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0" } }, "sha512-+QRrf0Igt8ccUWXHA+7doK5W6ODyhHdqVyblSlcQ8OciwkzIIGGEYNZom5OZyWMh+oI54lcSeyV2O3xaDepSrQ=="], + + "@angular/platform-browser": ["@angular/platform-browser@19.2.18", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "19.2.18", "@angular/common": "19.2.18", "@angular/core": "19.2.18" }, "optionalPeers": ["@angular/animations"] }, "sha512-eahtsHPyXTYLARs9YOlXhnXGgzw0wcyOcDkBvNWK/3lA0NHIgIHmQgXAmBo+cJ+g9skiEQTD2OmSrrwbFKWJkw=="], + + "@angular/platform-browser-dynamic": ["@angular/platform-browser-dynamic@19.2.18", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": "19.2.18", "@angular/compiler": "19.2.18", "@angular/core": "19.2.18", "@angular/platform-browser": "19.2.18" } }, "sha512-wqDtK2yVN5VDqVeOSOfqELdu40fyoIDknBGSxA27CEXzFVdMWJyIpuvUi+GMa+9eGjlS+1uVVBaRwxmnuvHj+A=="], + + "@angular/platform-server": ["@angular/platform-server@19.2.18", "", { "dependencies": { "tslib": "^2.3.0", "xhr2": "^0.2.0" }, "peerDependencies": { "@angular/common": "19.2.18", "@angular/compiler": "19.2.18", "@angular/core": "19.2.18", "@angular/platform-browser": "19.2.18", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-AWxrnFkO4VP10oePIIBTbICMroCgqfiBLOp1lUzDU55NRz/h+o6se9nFvux02tyakBVHkJTXewbQeZJ1xNERkQ=="], + + "@angular/router": ["@angular/router@19.2.18", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": "19.2.18", "@angular/core": "19.2.18", "@angular/platform-browser": "19.2.18", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-7cimxtPODSwokFQ0TRYzX0ad8Yjrl0MJfzaDCJejd1n/q7RZ7KZmHd0DS/LkDNXVMEh4swr00fK+3YWG/Szsrg=="], + + "@angular/ssr": ["@angular/ssr@19.2.19", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": "^19.0.0 || ^19.2.0-next.0", "@angular/core": "^19.0.0 || ^19.2.0-next.0", "@angular/platform-server": "^19.0.0 || ^19.2.0-next.0", "@angular/router": "^19.0.0 || ^19.2.0-next.0" }, "optionalPeers": ["@angular/platform-server"] }, "sha512-7HqC3K99DdzDakB/4mkqGqY6REQNMxskU1VVkH9D7SthZSuxhWIMVBojVhBDd+JOUYiyQlwEGMBevbrgbtfKlQ=="], + + "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], + + "@babel/core": ["@babel/core@7.26.10", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.10", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", "@babel/helpers": "^7.26.10", "@babel/parser": "^7.26.10", "@babel/template": "^7.26.9", "@babel/traverse": "^7.26.10", "@babel/types": "^7.26.10", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ=="], + + "@babel/generator": ["@babel/generator@7.26.10", "", { "dependencies": { "@babel/parser": "^7.26.10", "@babel/types": "^7.26.10", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang=="], + + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="], + + "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], + + "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.5", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "debug": "^4.4.1", "lodash.debounce": "^4.0.8", "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="], + + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="], + + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], + + "@babel/helper-split-export-declaration": ["@babel/helper-split-export-declaration@7.24.7", "", { "dependencies": { "@babel/types": "^7.24.7" } }, "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ=="], + + "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], + + "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q=="], + + "@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA=="], + + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA=="], + + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g=="], + + "@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="], + + "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="], + + "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="], + + "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="], + + "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="], + + "@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw=="], + + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.26.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A=="], + + "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], + + "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], + + "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="], + + "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="], + + "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="], + + "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="], + + "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="], + + "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="], + + "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="], + + "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], + + "@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="], + + "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], + + "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.26.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-remap-async-to-generator": "^7.25.9", "@babel/traverse": "^7.26.8" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg=="], + + "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.25.9", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", "@babel/helper-remap-async-to-generator": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ=="], + + "@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg=="], + + "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw=="], + + "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw=="], + + "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ=="], + + "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-replace-supers": "^7.28.6", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q=="], + + "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/template": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ=="], + + "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="], + + "@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg=="], + + "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q=="], + + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA=="], + + "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A=="], + + "@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw=="], + + "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="], + + "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="], + + "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="], + + "@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw=="], + + "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="], + + "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A=="], + + "@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ=="], + + "@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA=="], + + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], + + "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.28.5", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew=="], + + "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="], + + "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng=="], + + "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ=="], + + "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg=="], + + "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w=="], + + "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA=="], + + "@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng=="], + + "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ=="], + + "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w=="], + + "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="], + + "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg=="], + + "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA=="], + + "@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ=="], + + "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw=="], + + "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg=="], + + "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw=="], + + "@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.26.10", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.26.5", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw=="], + + "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="], + + "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA=="], + + "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="], + + "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="], + + "@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw=="], + + "@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg=="], + + "@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A=="], + + "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="], + + "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.28.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q=="], + + "@babel/preset-env": ["@babel/preset-env@7.26.9", "", { "dependencies": { "@babel/compat-data": "^7.26.8", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-validator-option": "^7.25.9", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.26.0", "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.25.9", "@babel/plugin-transform-async-generator-functions": "^7.26.8", "@babel/plugin-transform-async-to-generator": "^7.25.9", "@babel/plugin-transform-block-scoped-functions": "^7.26.5", "@babel/plugin-transform-block-scoping": "^7.25.9", "@babel/plugin-transform-class-properties": "^7.25.9", "@babel/plugin-transform-class-static-block": "^7.26.0", "@babel/plugin-transform-classes": "^7.25.9", "@babel/plugin-transform-computed-properties": "^7.25.9", "@babel/plugin-transform-destructuring": "^7.25.9", "@babel/plugin-transform-dotall-regex": "^7.25.9", "@babel/plugin-transform-duplicate-keys": "^7.25.9", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", "@babel/plugin-transform-dynamic-import": "^7.25.9", "@babel/plugin-transform-exponentiation-operator": "^7.26.3", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-for-of": "^7.26.9", "@babel/plugin-transform-function-name": "^7.25.9", "@babel/plugin-transform-json-strings": "^7.25.9", "@babel/plugin-transform-literals": "^7.25.9", "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", "@babel/plugin-transform-member-expression-literals": "^7.25.9", "@babel/plugin-transform-modules-amd": "^7.25.9", "@babel/plugin-transform-modules-commonjs": "^7.26.3", "@babel/plugin-transform-modules-systemjs": "^7.25.9", "@babel/plugin-transform-modules-umd": "^7.25.9", "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", "@babel/plugin-transform-new-target": "^7.25.9", "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", "@babel/plugin-transform-numeric-separator": "^7.25.9", "@babel/plugin-transform-object-rest-spread": "^7.25.9", "@babel/plugin-transform-object-super": "^7.25.9", "@babel/plugin-transform-optional-catch-binding": "^7.25.9", "@babel/plugin-transform-optional-chaining": "^7.25.9", "@babel/plugin-transform-parameters": "^7.25.9", "@babel/plugin-transform-private-methods": "^7.25.9", "@babel/plugin-transform-private-property-in-object": "^7.25.9", "@babel/plugin-transform-property-literals": "^7.25.9", "@babel/plugin-transform-regenerator": "^7.25.9", "@babel/plugin-transform-regexp-modifiers": "^7.26.0", "@babel/plugin-transform-reserved-words": "^7.25.9", "@babel/plugin-transform-shorthand-properties": "^7.25.9", "@babel/plugin-transform-spread": "^7.25.9", "@babel/plugin-transform-sticky-regex": "^7.25.9", "@babel/plugin-transform-template-literals": "^7.26.8", "@babel/plugin-transform-typeof-symbol": "^7.26.7", "@babel/plugin-transform-unicode-escapes": "^7.25.9", "@babel/plugin-transform-unicode-property-regex": "^7.25.9", "@babel/plugin-transform-unicode-regex": "^7.25.9", "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", "core-js-compat": "^3.40.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ=="], + + "@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="], + + "@babel/runtime": ["@babel/runtime@7.26.10", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], + + "@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="], + + "@discoveryjs/json-ext": ["@discoveryjs/json-ext@0.6.3", "", {}, "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.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" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], + + "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], + + "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], + + "@inquirer/ansi": ["@inquirer/ansi@1.0.2", "", {}, "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="], + + "@inquirer/confirm": ["@inquirer/confirm@5.1.6", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/type": "^3.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-6ZXYK3M1XmaVBZX6FCfChgtponnL0R6I7k8Nu+kaoNkT828FVZTcca1MqmWQipaW2oNREQl5AaPCUOOCVNdRMw=="], + + "@inquirer/core": ["@inquirer/core@10.3.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A=="], + + "@inquirer/figures": ["@inquirer/figures@1.0.15", "", {}, "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="], + + "@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], + + "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], + + "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], + + "@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="], + + "@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="], + + "@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="], + + "@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="], + + "@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="], + + "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="], + + "@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="], + + "@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="], + + "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], + + "@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="], + + "@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="], + + "@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="], + + "@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="], + + "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@jsonjoy.com/base64": ["@jsonjoy.com/base64@1.1.2", "", { "peerDependencies": { "tslib": "2" } }, "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA=="], + + "@jsonjoy.com/buffers": ["@jsonjoy.com/buffers@1.2.1", "", { "peerDependencies": { "tslib": "2" } }, "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA=="], + + "@jsonjoy.com/codegen": ["@jsonjoy.com/codegen@1.0.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g=="], + + "@jsonjoy.com/json-pack": ["@jsonjoy.com/json-pack@1.21.0", "", { "dependencies": { "@jsonjoy.com/base64": "^1.1.2", "@jsonjoy.com/buffers": "^1.2.0", "@jsonjoy.com/codegen": "^1.0.0", "@jsonjoy.com/json-pointer": "^1.0.2", "@jsonjoy.com/util": "^1.9.0", "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg=="], + + "@jsonjoy.com/json-pointer": ["@jsonjoy.com/json-pointer@1.0.2", "", { "dependencies": { "@jsonjoy.com/codegen": "^1.0.0", "@jsonjoy.com/util": "^1.9.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg=="], + + "@jsonjoy.com/util": ["@jsonjoy.com/util@1.9.0", "", { "dependencies": { "@jsonjoy.com/buffers": "^1.0.0", "@jsonjoy.com/codegen": "^1.0.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ=="], + + "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="], + + "@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="], + + "@lmdb/lmdb-darwin-arm64": ["@lmdb/lmdb-darwin-arm64@3.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-yF/ih9EJJZc72psFQbwnn8mExIWfTnzWJg+N02hnpXtDPETYLmQswIMBn7+V88lfCaFrMozJsUvcEQIkEPU0Gg=="], + + "@lmdb/lmdb-darwin-x64": ["@lmdb/lmdb-darwin-x64@3.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-5BbCumsFLbCi586Bb1lTWQFkekdQUw8/t8cy++Uq251cl3hbDIGEwD9HAwh8H6IS2F6QA9KdKmO136LmipRNkg=="], + + "@lmdb/lmdb-linux-arm": ["@lmdb/lmdb-linux-arm@3.2.6", "", { "os": "linux", "cpu": "arm" }, "sha512-+6XgLpMb7HBoWxXj+bLbiiB4s0mRRcDPElnRS3LpWRzdYSe+gFk5MT/4RrVNqd2MESUDmb53NUXw1+BP69bjiQ=="], + + "@lmdb/lmdb-linux-arm64": ["@lmdb/lmdb-linux-arm64@3.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-l5VmJamJ3nyMmeD1ANBQCQqy7do1ESaJQfKPSm2IG9/ADZryptTyCj8N6QaYgIWewqNUrcbdMkJajRQAt5Qjfg=="], + + "@lmdb/lmdb-linux-x64": ["@lmdb/lmdb-linux-x64@3.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-nDYT8qN9si5+onHYYaI4DiauDMx24OAiuZAUsEqrDy+ja/3EbpXPX/VAkMV8AEaQhy3xc4dRC+KcYIvOFefJ4Q=="], + + "@lmdb/lmdb-win32-x64": ["@lmdb/lmdb-win32-x64@3.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-XlqVtILonQnG+9fH2N3Aytria7P/1fwDgDhl29rde96uH2sLB8CHORIf2PfuLVzFQJ7Uqp8py9AYwr3ZUCFfWg=="], + + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], + + "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], + + "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], + + "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], + + "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], + + "@napi-rs/nice": ["@napi-rs/nice@1.1.1", "", { "optionalDependencies": { "@napi-rs/nice-android-arm-eabi": "1.1.1", "@napi-rs/nice-android-arm64": "1.1.1", "@napi-rs/nice-darwin-arm64": "1.1.1", "@napi-rs/nice-darwin-x64": "1.1.1", "@napi-rs/nice-freebsd-x64": "1.1.1", "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", "@napi-rs/nice-linux-arm64-gnu": "1.1.1", "@napi-rs/nice-linux-arm64-musl": "1.1.1", "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", "@napi-rs/nice-linux-s390x-gnu": "1.1.1", "@napi-rs/nice-linux-x64-gnu": "1.1.1", "@napi-rs/nice-linux-x64-musl": "1.1.1", "@napi-rs/nice-openharmony-arm64": "1.1.1", "@napi-rs/nice-win32-arm64-msvc": "1.1.1", "@napi-rs/nice-win32-ia32-msvc": "1.1.1", "@napi-rs/nice-win32-x64-msvc": "1.1.1" } }, "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw=="], + + "@napi-rs/nice-android-arm-eabi": ["@napi-rs/nice-android-arm-eabi@1.1.1", "", { "os": "android", "cpu": "arm" }, "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw=="], + + "@napi-rs/nice-android-arm64": ["@napi-rs/nice-android-arm64@1.1.1", "", { "os": "android", "cpu": "arm64" }, "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw=="], + + "@napi-rs/nice-darwin-arm64": ["@napi-rs/nice-darwin-arm64@1.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A=="], + + "@napi-rs/nice-darwin-x64": ["@napi-rs/nice-darwin-x64@1.1.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ=="], + + "@napi-rs/nice-freebsd-x64": ["@napi-rs/nice-freebsd-x64@1.1.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ=="], + + "@napi-rs/nice-linux-arm-gnueabihf": ["@napi-rs/nice-linux-arm-gnueabihf@1.1.1", "", { "os": "linux", "cpu": "arm" }, "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg=="], + + "@napi-rs/nice-linux-arm64-gnu": ["@napi-rs/nice-linux-arm64-gnu@1.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ=="], + + "@napi-rs/nice-linux-arm64-musl": ["@napi-rs/nice-linux-arm64-musl@1.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg=="], + + "@napi-rs/nice-linux-ppc64-gnu": ["@napi-rs/nice-linux-ppc64-gnu@1.1.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg=="], + + "@napi-rs/nice-linux-riscv64-gnu": ["@napi-rs/nice-linux-riscv64-gnu@1.1.1", "", { "os": "linux", "cpu": "none" }, "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw=="], + + "@napi-rs/nice-linux-s390x-gnu": ["@napi-rs/nice-linux-s390x-gnu@1.1.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ=="], + + "@napi-rs/nice-linux-x64-gnu": ["@napi-rs/nice-linux-x64-gnu@1.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg=="], + + "@napi-rs/nice-linux-x64-musl": ["@napi-rs/nice-linux-x64-musl@1.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw=="], + + "@napi-rs/nice-openharmony-arm64": ["@napi-rs/nice-openharmony-arm64@1.1.1", "", { "os": "none", "cpu": "arm64" }, "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ=="], + + "@napi-rs/nice-win32-arm64-msvc": ["@napi-rs/nice-win32-arm64-msvc@1.1.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA=="], + + "@napi-rs/nice-win32-ia32-msvc": ["@napi-rs/nice-win32-ia32-msvc@1.1.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug=="], + + "@napi-rs/nice-win32-x64-msvc": ["@napi-rs/nice-win32-x64-msvc@1.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ=="], + + "@ngtools/webpack": ["@ngtools/webpack@19.2.19", "", { "peerDependencies": { "@angular/compiler-cli": "^19.0.0 || ^19.2.0-next.0", "typescript": ">=5.5 <5.9", "webpack": "^5.54.0" } }, "sha512-R9aeTrOBiRVl8I698JWPniUAAEpSvzc8SUGWSM5UXWMcHnWqd92cOnJJ1aXDGJZKXrbhMhCBx9Dglmcks5IDpg=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@parcel/watcher": ["@parcel/watcher@2.5.4", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.4", "@parcel/watcher-darwin-arm64": "2.5.4", "@parcel/watcher-darwin-x64": "2.5.4", "@parcel/watcher-freebsd-x64": "2.5.4", "@parcel/watcher-linux-arm-glibc": "2.5.4", "@parcel/watcher-linux-arm-musl": "2.5.4", "@parcel/watcher-linux-arm64-glibc": "2.5.4", "@parcel/watcher-linux-arm64-musl": "2.5.4", "@parcel/watcher-linux-x64-glibc": "2.5.4", "@parcel/watcher-linux-x64-musl": "2.5.4", "@parcel/watcher-win32-arm64": "2.5.4", "@parcel/watcher-win32-ia32": "2.5.4", "@parcel/watcher-win32-x64": "2.5.4" } }, "sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ=="], + + "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.4", "", { "os": "android", "cpu": "arm64" }, "sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g=="], + + "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw=="], + + "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg=="], + + "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q=="], + + "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.4", "", { "os": "linux", "cpu": "arm" }, "sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw=="], + + "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ=="], + + "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw=="], + + "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ=="], + + "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA=="], + + "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg=="], + + "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ=="], + + "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg=="], + + "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.8", "", { "os": "android", "cpu": "arm" }, "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ=="], + + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.1", "", { "os": "none", "cpu": "arm64" }, "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.8", "", { "os": "win32", "cpu": "x64" }, "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], + + "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], + + "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], + + "@tootallnate/once": ["@tootallnate/once@2.0.0", "", {}, "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="], + + "@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], + + "@types/bonjour": ["@types/bonjour@3.5.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ=="], + + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + + "@types/connect-history-api-fallback": ["@types/connect-history-api-fallback@1.5.4", "", { "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw=="], + + "@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="], + + "@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/express": ["@types/express@4.17.25", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw=="], + + "@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.8", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA=="], + + "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="], + + "@types/hammerjs": ["@types/hammerjs@2.0.46", "", {}, "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="], + + "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], + + "@types/http-proxy": ["@types/http-proxy@1.17.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw=="], + + "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], + + "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], + + "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], + + "@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="], + + "@types/jsdom": ["@types/jsdom@20.0.1", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], + + "@types/node": ["@types/node@20.19.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g=="], + + "@types/node-forge": ["@types/node-forge@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw=="], + + "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], + + "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], + + "@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="], + + "@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], + + "@types/serve-index": ["@types/serve-index@1.9.4", "", { "dependencies": { "@types/express": "*" } }, "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug=="], + + "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="], + + "@types/sockjs": ["@types/sockjs@0.3.36", "", { "dependencies": { "@types/node": "*" } }, "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q=="], + + "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], + + "@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="], + + "@types/three": ["@types/three@0.182.0", "", { "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": ">=0.5.17", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.22.0" } }, "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q=="], + + "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="], + + "@types/webxr": ["@types/webxr@0.5.24", "", {}, "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="], + + "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.53.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.53.0", "@typescript-eslint/types": "^8.53.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.53.0", "", { "dependencies": { "@typescript-eslint/types": "8.53.0", "@typescript-eslint/visitor-keys": "8.53.0" } }, "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.53.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.53.0", "", {}, "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.53.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.53.0", "@typescript-eslint/tsconfig-utils": "8.53.0", "@typescript-eslint/types": "8.53.0", "@typescript-eslint/visitor-keys": "8.53.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.53.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.53.0", "@typescript-eslint/types": "8.53.0", "@typescript-eslint/typescript-estree": "8.53.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.53.0", "", { "dependencies": { "@typescript-eslint/types": "8.53.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@1.2.0", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "sha512-mkQnxTkcldAzIsomk1UuLfAu9n+kpQ3JbHcpCp7d2Oo6ITtji8pHS3QToOWjhPFvNQSnhlkAjmGbhv2QvwO/7Q=="], + + "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], + + "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], + + "@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="], + + "@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="], + + "@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="], + + "@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="], + + "@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="], + + "@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="], + + "@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="], + + "@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="], + + "@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="], + + "@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="], + + "@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="], + + "@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="], + + "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="], + + "@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="], + + "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], + + "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], + + "abab": ["abab@2.0.6", "", {}, "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="], + + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-globals": ["acorn-globals@7.0.1", "", { "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" } }, "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], + + "adjust-sourcemap-loader": ["adjust-sourcemap-loader@4.0.0", "", { "dependencies": { "loader-utils": "^2.0.0", "regex-parser": "^2.2.11" } }, "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ajv": ["ajv@6.12.6", "", { "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" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], + + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + + "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], + + "ansi-html-community": ["ansi-html-community@0.0.8", "", { "bin": { "ansi-html": "bin/ansi-html" } }, "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "autoprefixer": ["autoprefixer@10.4.23", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="], + + "babel-loader": ["babel-loader@9.2.1", "", { "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" }, "peerDependencies": { "@babel/core": "^7.12.0", "webpack": ">=5" } }, "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA=="], + + "babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="], + + "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="], + + "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.14", "", { "dependencies": { "@babel/compat-data": "^7.27.7", "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg=="], + + "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.11.1", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.3", "core-js-compat": "^3.40.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ=="], + + "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="], + + "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="], + + "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.15", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg=="], + + "batch": ["batch@0.6.1", "", {}, "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw=="], + + "beasties": ["beasties@0.3.2", "", { "dependencies": { "css-select": "^5.1.0", "css-what": "^6.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "htmlparser2": "^10.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.49", "postcss-media-query-parser": "^0.2.3" } }, "sha512-p4AF8uYzm9Fwu8m/hSVTCPXrRBPmB34hQpHsec2KOaR9CZmgoU8IOv4Cvwq4hgz2p4hLMNbsdNl5XeA6XbAQwA=="], + + "big.js": ["big.js@5.2.2", "", {}, "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="], + + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="], + + "bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + + "bs-logger": ["bs-logger@0.2.6", "", { "dependencies": { "fast-json-stable-stringify": "2.x" } }, "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog=="], + + "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], + + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], + + "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001764", "", {}, "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], + + "chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="], + + "chartjs-plugin-datalabels": ["chartjs-plugin-datalabels@2.2.0", "", { "peerDependencies": { "chart.js": ">=3.0.0" } }, "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw=="], + + "chartjs-plugin-zoom": ["chartjs-plugin-zoom@2.2.0", "", { "dependencies": { "@types/hammerjs": "^2.0.45", "hammerjs": "^2.0.8" }, "peerDependencies": { "chart.js": ">=3.2.0" } }, "sha512-in6kcdiTlP6npIVLMd4zXZ08PDUXC52gZ4FAy5oyjk1zX3gKarXMAof7B9eFiisf9WOC3bh2saHg+J5WtLXZeA=="], + + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + + "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], + + "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], + + "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], + + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], + + "clone-deep": ["clone-deep@4.0.1", "", { "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" } }, "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ=="], + + "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="], + + "collect-v8-coverage": ["collect-v8-coverage@1.0.3", "", {}, "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "common-path-prefix": ["common-path-prefix@3.0.0", "", {}, "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w=="], + + "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="], + + "compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "connect-history-api-fallback": ["connect-history-api-fallback@2.0.0", "", {}, "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA=="], + + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="], + + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], + + "copy-anything": ["copy-anything@2.0.6", "", { "dependencies": { "is-what": "^3.14.1" } }, "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw=="], + + "copy-webpack-plugin": ["copy-webpack-plugin@12.0.2", "", { "dependencies": { "fast-glob": "^3.3.2", "glob-parent": "^6.0.1", "globby": "^14.0.0", "normalize-path": "^3.0.0", "schema-utils": "^4.2.0", "serialize-javascript": "^6.0.2" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA=="], + + "core-js-compat": ["core-js-compat@3.47.0", "", { "dependencies": { "browserslist": "^4.28.0" } }, "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], + + "create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css-loader": ["css-loader@7.1.2", "", { "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" }, "peerDependencies": { "@rspack/core": "0.x || 1.x", "webpack": "^5.27.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA=="], + + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="], + + "cssstyle": ["cssstyle@2.3.0", "", { "dependencies": { "cssom": "~0.3.6" } }, "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A=="], + + "data-urls": ["data-urls@3.0.2", "", { "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0" } }, "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + + "dedent": ["dedent@1.7.1", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], + + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + + "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], + + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], + + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + + "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], + + "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], + + "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + + "dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="], + + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domexception": ["domexception@4.0.0", "", { "dependencies": { "webidl-conversions": "^7.0.0" } }, "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], + + "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "emojis-list": ["emojis-list@3.0.0", "", {}, "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="], + + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + + "errno": ["errno@0.1.8", "", { "dependencies": { "prr": "~1.0.1" }, "bin": { "errno": "cli.js" } }, "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A=="], + + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + + "esbuild-wasm": ["esbuild-wasm@0.25.4", "", { "bin": { "esbuild": "bin/esbuild" } }, "sha512-2HlCS6rNvKWaSKhWaG/YIyRsTsL3gUrMP2ToZMBIjw9LM7vVcIs+rz8kE2vExvTJgvM8OKPqNpcHawY/BQc/qQ=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + + "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="], + + "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="], + + "express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "faye-websocket": ["faye-websocket@0.11.4", "", { "dependencies": { "websocket-driver": ">=0.5.1" } }, "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g=="], + + "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + + "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], + + "find-cache-dir": ["find-cache-dir@4.0.0", "", { "dependencies": { "common-path-prefix": "^3.0.0", "pkg-dir": "^7.0.0" } }, "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="], + + "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], + + "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "glob-to-regex.js": ["glob-to-regex.js@1.2.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ=="], + + "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], + + "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], + + "globby": ["globby@14.1.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.3", "ignore": "^7.0.3", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "hammerjs": ["hammerjs@2.0.8", "", {}, "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ=="], + + "handle-thing": ["handle-thing@2.0.1", "", {}, "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg=="], + + "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hpack.js": ["hpack.js@2.1.6", "", { "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", "readable-stream": "^2.0.1", "wbuf": "^1.1.0" } }, "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ=="], + + "html-encoding-sniffer": ["html-encoding-sniffer@3.0.0", "", { "dependencies": { "whatwg-encoding": "^2.0.0" } }, "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + + "htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="], + + "http-deceiver": ["http-deceiver@1.2.7", "", {}, "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw=="], + + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + + "http-parser-js": ["http-parser-js@0.5.10", "", {}, "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA=="], + + "http-proxy": ["http-proxy@1.18.1", "", { "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ=="], + + "http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="], + + "http-proxy-middleware": ["http-proxy-middleware@3.0.5", "", { "dependencies": { "@types/http-proxy": "^1.17.15", "debug": "^4.3.6", "http-proxy": "^1.18.1", "is-glob": "^4.0.3", "is-plain-object": "^5.0.0", "micromatch": "^4.0.8" } }, "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "hyperdyperid": ["hyperdyperid@1.2.0", "", {}, "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "icss-utils": ["icss-utils@5.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "image-size": ["image-size@0.5.5", "", { "bin": { "image-size": "bin/image-size.js" } }, "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ=="], + + "immutable": ["immutable@5.1.4", "", {}, "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], + + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], + + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], + + "is-network-error": ["is-network-error@1.3.0", "", {}, "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + + "is-plain-obj": ["is-plain-obj@3.0.0", "", {}, "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA=="], + + "is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="], + + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], + + "is-what": ["is-what@3.14.1", "", {}, "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA=="], + + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "isobject": ["isobject@3.0.1", "", {}, "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="], + + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + + "jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="], + + "jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="], + + "jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="], + + "jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="], + + "jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="], + + "jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="], + + "jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="], + + "jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="], + + "jest-environment-jsdom": ["jest-environment-jsdom@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/jsdom": "^20.0.0", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0", "jsdom": "^20.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA=="], + + "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="], + + "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="], + + "jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="], + + "jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="], + + "jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="], + + "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="], + + "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="], + + "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="], + + "jest-preset-angular": ["jest-preset-angular@13.1.6", "", { "dependencies": { "bs-logger": "^0.2.6", "esbuild-wasm": ">=0.13.8", "jest-environment-jsdom": "^29.0.0", "jest-util": "^29.0.0", "pretty-format": "^29.0.0", "ts-jest": "^29.0.0" }, "optionalDependencies": { "esbuild": ">=0.13.8" }, "peerDependencies": { "@angular-devkit/build-angular": ">=13.0.0 <18.0.0", "@angular/compiler-cli": ">=13.0.0 <18.0.0", "@angular/core": ">=13.0.0 <18.0.0", "@angular/platform-browser-dynamic": ">=13.0.0 <18.0.0", "jest": "^29.0.0", "typescript": ">=4.4" } }, "sha512-0pXSm6168Qn+qKp7DpzYoaIp0uyMHdQaWYVp8jlw7Mh+NEBtrBjKqts3kLeBHgAhGMQArp07S2IxZ6eCr8fc7Q=="], + + "jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="], + + "jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="], + + "jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="], + + "jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="], + + "jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="], + + "jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="], + + "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="], + + "jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="], + + "jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="], + + "jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], + + "jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsdom": ["jsdom@20.0.3", "", { "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", "acorn-globals": "^7.0.0", "cssom": "^0.5.0", "cssstyle": "^2.3.0", "data-urls": "^3.0.2", "decimal.js": "^10.4.2", "domexception": "^4.0.0", "escodegen": "^2.0.0", "form-data": "^4.0.0", "html-encoding-sniffer": "^3.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.2", "parse5": "^7.1.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.2", "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^2.0.0", "whatwg-mimetype": "^3.0.0", "whatwg-url": "^11.0.0", "ws": "^8.11.0", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "canvas": "^2.5.0" }, "optionalPeers": ["canvas"] }, "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "karma-source-map-support": ["karma-source-map-support@1.4.0", "", { "dependencies": { "source-map-support": "^0.5.5" } }, "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "launch-editor": ["launch-editor@2.12.0", "", { "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" } }, "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg=="], + + "less": ["less@4.2.2", "", { "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", "tslib": "^2.3.0" }, "optionalDependencies": { "errno": "^0.1.1", "graceful-fs": "^4.1.2", "image-size": "~0.5.0", "make-dir": "^2.1.0", "mime": "^1.4.1", "needle": "^3.1.0", "source-map": "~0.6.0" }, "bin": { "lessc": "bin/lessc" } }, "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg=="], + + "less-loader": ["less-loader@12.2.0", "", { "peerDependencies": { "@rspack/core": "0.x || 1.x", "less": "^3.5.0 || ^4.0.0", "webpack": "^5.0.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg=="], + + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "license-webpack-plugin": ["license-webpack-plugin@4.0.2", "", { "dependencies": { "webpack-sources": "^3.0.0" } }, "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "listr2": ["listr2@8.2.5", "", { "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ=="], + + "lmdb": ["lmdb@3.2.6", "", { "dependencies": { "msgpackr": "^1.11.2", "node-addon-api": "^6.1.0", "node-gyp-build-optional-packages": "5.2.2", "ordered-binary": "^1.5.3", "weak-lru-cache": "^1.2.2" }, "optionalDependencies": { "@lmdb/lmdb-darwin-arm64": "3.2.6", "@lmdb/lmdb-darwin-x64": "3.2.6", "@lmdb/lmdb-linux-arm": "3.2.6", "@lmdb/lmdb-linux-arm64": "3.2.6", "@lmdb/lmdb-linux-x64": "3.2.6", "@lmdb/lmdb-win32-x64": "3.2.6" }, "bin": { "download-lmdb-prebuilds": "bin/download-prebuilds.js" } }, "sha512-SuHqzPl7mYStna8WRotY8XX/EUZBjjv3QyKIByeCLFfC9uXT/OIHByEcA07PzbMfQAM0KYJtLgtpMRlIe5dErQ=="], + + "loader-runner": ["loader-runner@4.3.1", "", {}, "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q=="], + + "loader-utils": ["loader-utils@3.3.1", "", {}, "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash-es": ["lodash-es@4.17.22", "", {}, "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q=="], + + "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + + "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + + "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "make-dir": ["make-dir@2.1.0", "", { "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" } }, "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA=="], + + "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="], + + "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + + "memfs": ["memfs@4.53.0", "", { "dependencies": { "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", "thingies": "^2.5.0", "tree-dump": "^1.0.3", "tslib": "^2.0.0" } }, "sha512-TKFRsKjJA30iAc9ZeGH/77v5nLcNUD0GBOL/tAj4O63RPIKNxGDZ54ZyuQM4KjEKEj7gfer/Ta1xAzB+HrEnrA=="], + + "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "meshoptimizer": ["meshoptimizer@0.22.0", "", {}, "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg=="], + + "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "mini-css-extract-plugin": ["mini-css-extract-plugin@2.9.2", "", { "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" }, "peerDependencies": { "webpack": "^5.0.0" } }, "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w=="], + + "minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "msgpackr": ["msgpackr@1.11.8", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA=="], + + "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], + + "multicast-dns": ["multicast-dns@7.2.5", "", { "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg=="], + + "mute-stream": ["mute-stream@2.0.0", "", {}, "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "needle": ["needle@3.3.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "sax": "^1.2.4" }, "bin": { "needle": "bin/needle" } }, "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q=="], + + "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + + "ng2-charts": ["ng2-charts@5.0.4", "", { "dependencies": { "lodash-es": "^4.17.15", "tslib": "^2.3.0" }, "peerDependencies": { "@angular/cdk": ">=16.0.0", "@angular/common": ">=16.0.0", "@angular/core": ">=16.0.0", "@angular/platform-browser": ">=16.0.0", "chart.js": "^3.4.0 || ^4.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-AnOZ2KSRw7QjiMMNtXz9tdnO+XrIKP/2MX1TfqEEo2fwFU5c8LFJIYqmkMPkIzAEm/U9y/1psA5TDNmxxjEdgA=="], + + "node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], + + "node-forge": ["node-forge@1.3.3", "", {}, "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg=="], + + "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + + "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], + + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="], + + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "nwsapi": ["nwsapi@2.2.23", "", {}, "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "obuf": ["obuf@1.1.2", "", {}, "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "open": ["open@10.1.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw=="], + + "optionator": ["optionator@0.9.4", "", { "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" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], + + "ordered-binary": ["ordered-binary@1.6.1", "", {}, "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "p-retry": ["p-retry@6.2.1", "", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + + "parse-node-version": ["parse-node-version@1.0.1", "", {}, "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA=="], + + "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="], + + "parse5-html-rewriting-stream": ["parse5-html-rewriting-stream@7.0.0", "", { "dependencies": { "entities": "^4.3.0", "parse5": "^7.0.0", "parse5-sax-parser": "^7.0.0" } }, "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg=="], + + "parse5-sax-parser": ["parse5-sax-parser@7.0.0", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg=="], + + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="], + + "path-type": ["path-type@6.0.0", "", {}, "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "piscina": ["piscina@4.8.0", "", { "optionalDependencies": { "@napi-rs/nice": "^1.0.1" } }, "sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA=="], + + "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], + + "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "postcss-loader": ["postcss-loader@8.1.1", "", { "dependencies": { "cosmiconfig": "^9.0.0", "jiti": "^1.20.0", "semver": "^7.5.4" }, "peerDependencies": { "@rspack/core": "0.x || 1.x", "postcss": "^7.0.0 || ^8.0.1", "webpack": "^5.0.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ=="], + + "postcss-media-query-parser": ["postcss-media-query-parser@0.2.3", "", {}, "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig=="], + + "postcss-modules-extract-imports": ["postcss-modules-extract-imports@3.1.0", "", { "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q=="], + + "postcss-modules-local-by-default": ["postcss-modules-local-by-default@4.2.0", "", { "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw=="], + + "postcss-modules-scope": ["postcss-modules-scope@3.2.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA=="], + + "postcss-modules-values": ["postcss-modules-values@4.0.0", "", { "dependencies": { "icss-utils": "^5.0.0" }, "peerDependencies": { "postcss": "^8.1.0" } }, "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ=="], + + "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], + + "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + + "prr": ["prr@1.0.1", "", {}, "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw=="], + + "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + + "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], + + "querystringify": ["querystringify@2.2.0", "", {}, "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], + + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="], + + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + + "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], + + "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="], + + "regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + + "regex-parser": ["regex-parser@2.3.1", "", {}, "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ=="], + + "regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="], + + "regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], + + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], + + "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "resolve-url-loader": ["resolve-url-loader@5.0.0", "", { "dependencies": { "adjust-sourcemap-loader": "^4.0.0", "convert-source-map": "^1.7.0", "loader-utils": "^2.0.0", "postcss": "^8.2.14", "source-map": "0.6.1" } }, "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg=="], + + "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="], + + "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], + + "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "rollup": ["rollup@4.34.8", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.8", "@rollup/rollup-android-arm64": "4.34.8", "@rollup/rollup-darwin-arm64": "4.34.8", "@rollup/rollup-darwin-x64": "4.34.8", "@rollup/rollup-freebsd-arm64": "4.34.8", "@rollup/rollup-freebsd-x64": "4.34.8", "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", "@rollup/rollup-linux-arm-musleabihf": "4.34.8", "@rollup/rollup-linux-arm64-gnu": "4.34.8", "@rollup/rollup-linux-arm64-musl": "4.34.8", "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", "@rollup/rollup-linux-riscv64-gnu": "4.34.8", "@rollup/rollup-linux-s390x-gnu": "4.34.8", "@rollup/rollup-linux-x64-gnu": "4.34.8", "@rollup/rollup-linux-x64-musl": "4.34.8", "@rollup/rollup-win32-arm64-msvc": "4.34.8", "@rollup/rollup-win32-ia32-msvc": "4.34.8", "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ=="], + + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "sass": ["sass@1.85.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww=="], + + "sass-loader": ["sass-loader@16.0.5", "", { "dependencies": { "neo-async": "^2.6.2" }, "peerDependencies": { "@rspack/core": "0.x || 1.x", "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", "sass-embedded": "*", "webpack": "^5.0.0" }, "optionalPeers": ["@rspack/core", "node-sass", "sass", "sass-embedded", "webpack"] }, "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw=="], + + "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + + "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], + + "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], + + "select-hose": ["select-hose@2.0.0", "", {}, "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg=="], + + "selfsigned": ["selfsigned@2.4.1", "", { "dependencies": { "@types/node-forge": "^1.3.0", "node-forge": "^1" } }, "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q=="], + + "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + + "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], + + "serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="], + + "serve-index": ["serve-index@1.9.1", "", { "dependencies": { "accepts": "~1.3.4", "batch": "0.6.1", "debug": "2.6.9", "escape-html": "~1.0.3", "http-errors": "~1.6.2", "mime-types": "~2.1.17", "parseurl": "~1.3.2" } }, "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw=="], + + "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shallow-clone": ["shallow-clone@3.0.1", "", { "dependencies": { "kind-of": "^6.0.2" } }, "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + + "side-channel": ["side-channel@1.1.0", "", { "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" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "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" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], + + "sockjs": ["sockjs@0.3.24", "", { "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", "websocket-driver": "^0.7.4" } }, "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ=="], + + "source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-loader": ["source-map-loader@5.0.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" }, "peerDependencies": { "webpack": "^5.72.1" } }, "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "spdy": ["spdy@4.0.2", "", { "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", "http-deceiver": "^1.2.7", "select-hose": "^2.0.0", "spdy-transport": "^3.0.0" } }, "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA=="], + + "spdy-transport": ["spdy-transport@3.0.0", "", { "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", "hpack.js": "^2.1.6", "obuf": "^1.1.2", "readable-stream": "^3.0.6", "wbuf": "^1.7.3" } }, "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + + "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], + + "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], + + "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "terser": ["terser@5.39.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw=="], + + "terser-webpack-plugin": ["terser-webpack-plugin@5.3.16", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q=="], + + "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "thingies": ["thingies@2.5.0", "", { "peerDependencies": { "tslib": "^2" } }, "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw=="], + + "three": ["three@0.182.0", "", {}, "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ=="], + + "thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="], + + "tr46": ["tr46@3.0.0", "", { "dependencies": { "punycode": "^2.1.1" } }, "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA=="], + + "tree-dump": ["tree-dump@1.1.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "ts-jest": ["ts-jest@29.4.6", "", { "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", "handlebars": "^4.7.8", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.3", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/transform": "^29.0.0 || ^30.0.0", "@jest/types": "^29.0.0 || ^30.0.0", "babel-jest": "^29.0.0 || ^30.0.0", "jest": "^29.0.0 || ^30.0.0", "jest-util": "^29.0.0 || ^30.0.0", "typescript": ">=4.3 <6" }, "optionalPeers": ["@babel/core", "@jest/transform", "@jest/types", "babel-jest", "jest-util"], "bin": { "ts-jest": "cli.js" } }, "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], + + "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], + + "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], + + "typed-assert": ["typed-assert@1.0.9", "", {}, "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="], + + "unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="], + + "unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], + + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], + + "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], + + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], + + "w3c-xmlserializer": ["w3c-xmlserializer@4.0.0", "", { "dependencies": { "xml-name-validator": "^4.0.0" } }, "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw=="], + + "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], + + "watchpack": ["watchpack@2.4.2", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw=="], + + "wbuf": ["wbuf@1.7.3", "", { "dependencies": { "minimalistic-assert": "^1.0.0" } }, "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA=="], + + "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], + + "weak-lru-cache": ["weak-lru-cache@1.2.2", "", {}, "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw=="], + + "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], + + "webpack": ["webpack@5.98.0", "", { "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.14.0", "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA=="], + + "webpack-dev-middleware": ["webpack-dev-middleware@7.4.2", "", { "dependencies": { "colorette": "^2.0.10", "memfs": "^4.6.0", "mime-types": "^2.1.31", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" }, "peerDependencies": { "webpack": "^5.0.0" }, "optionalPeers": ["webpack"] }, "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA=="], + + "webpack-dev-server": ["webpack-dev-server@5.2.2", "", { "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", "@types/express": "^4.17.21", "@types/express-serve-static-core": "^4.17.21", "@types/serve-index": "^1.9.4", "@types/serve-static": "^1.15.5", "@types/sockjs": "^0.3.36", "@types/ws": "^8.5.10", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.2.1", "chokidar": "^3.6.0", "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^2.0.0", "express": "^4.21.2", "graceful-fs": "^4.2.6", "http-proxy-middleware": "^2.0.9", "ipaddr.js": "^2.1.0", "launch-editor": "^2.6.1", "open": "^10.0.3", "p-retry": "^6.2.0", "schema-utils": "^4.2.0", "selfsigned": "^2.4.1", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", "webpack-dev-middleware": "^7.4.2", "ws": "^8.18.0" }, "peerDependencies": { "webpack": "^5.0.0" }, "optionalPeers": ["webpack"], "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" } }, "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg=="], + + "webpack-merge": ["webpack-merge@6.0.1", "", { "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", "wildcard": "^2.0.1" } }, "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg=="], + + "webpack-sources": ["webpack-sources@3.3.3", "", {}, "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg=="], + + "webpack-subresource-integrity": ["webpack-subresource-integrity@5.1.0", "", { "dependencies": { "typed-assert": "^1.0.8" }, "peerDependencies": { "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", "webpack": "^5.12.0" }, "optionalPeers": ["html-webpack-plugin"] }, "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q=="], + + "websocket-driver": ["websocket-driver@0.7.4", "", { "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg=="], + + "websocket-extensions": ["websocket-extensions@0.1.4", "", {}, "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="], + + "whatwg-encoding": ["whatwg-encoding@2.0.0", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg=="], + + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], + + "whatwg-url": ["whatwg-url@11.0.0", "", { "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wildcard": ["wildcard@2.0.1", "", {}, "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="], + + "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + + "xhr2": ["xhr2@0.2.1", "", {}, "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw=="], + + "xml-name-validator": ["xml-name-validator@4.0.0", "", {}, "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="], + + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="], + + "zone.js": ["zone.js@0.14.10", "", {}, "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ=="], + + "@angular-devkit/architect/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], + + "@angular-devkit/build-angular/autoprefixer": ["autoprefixer@10.4.20", "", { "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g=="], + + "@angular-devkit/build-angular/postcss": ["postcss@8.5.2", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA=="], + + "@angular-devkit/build-angular/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], + + "@angular-devkit/build-webpack/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], + + "@angular-devkit/core/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "@angular-devkit/core/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], + + "@angular-devkit/schematics/rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], + + "@angular-eslint/schematics/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "@angular/compiler-cli/@babel/core": ["@babel/core@7.26.9", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.9", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", "@babel/helpers": "^7.26.9", "@babel/parser": "^7.26.9", "@babel/template": "^7.26.9", "@babel/traverse": "^7.26.9", "@babel/types": "^7.26.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw=="], + + "@angular/compiler-cli/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "@angular/compiler-cli/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "@babel/core/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-create-class-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-create-regexp-features-plugin/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/helper-create-regexp-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-remap-async-to-generator/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/plugin-transform-classes/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/plugin-transform-private-property-in-object/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], + + "@babel/plugin-transform-runtime/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/preset-env/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/traverse/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + + "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + + "@istanbuljs/load-nyc-config/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "@jest/reporters/jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="], + + "@jest/transform/@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], + + "@jest/transform/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "@parcel/watcher/node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "@parcel/watcher/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "@types/jsdom/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "adjust-sourcemap-loader/loader-utils": ["loader-utils@2.0.4", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^2.1.2" } }, "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw=="], + + "ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ajv-keywords/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], + + "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "cli-truncate/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "clone-deep/is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="], + + "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "compression/negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], + + "css-loader/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="], + + "escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "eslint/eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], + + "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "find-cache-dir/pkg-dir": ["pkg-dir@7.0.0", "", { "dependencies": { "find-up": "^6.3.0" } }, "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA=="], + + "globby/slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], + + "handlebars/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "hpack.js/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "http-proxy/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + + "http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "istanbul-lib-instrument/@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], + + "istanbul-lib-instrument/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "istanbul-lib-report/make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + + "istanbul-lib-source-maps/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "jest-config/@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], + + "jest-haste-map/jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="], + + "jest-preset-angular/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], + + "jest-preset-angular/esbuild-wasm": ["esbuild-wasm@0.27.2", "", { "bin": { "esbuild": "bin/esbuild" } }, "sha512-eUTnl8eh+v8UZIZh4MrMOKDAc8Lm7+NqP3pyuTORGFY1s/o9WoiJgKnwXy+te2J3hX7iRbFSHEyig7GsPeeJyw=="], + + "jest-runner/jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="], + + "jest-runner/source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="], + + "jest-snapshot/@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], + + "jest-snapshot/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "jest-snapshot/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "jsdom/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "jsdom/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "less/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "log-update/ansi-escapes": ["ansi-escapes@7.2.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw=="], + + "log-update/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "log-update/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "log-update/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "make-dir/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "parse5-html-rewriting-stream/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "parse5-sax-parser/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "postcss-loader/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "postcss-modules-local-by-default/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + + "postcss-modules-scope/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + + "proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + + "raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + + "read-cache/pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], + + "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "resolve-cwd/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "resolve-url-loader/loader-utils": ["loader-utils@2.0.4", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^2.1.2" } }, "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw=="], + + "resolve-url-loader/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "rollup/@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], + + "sass/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "schema-utils/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "schema-utils/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "serve-index/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + + "serve-index/http-errors": ["http-errors@1.6.3", "", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", "statuses": ">= 1.4.0 < 2" } }, "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A=="], + + "slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "ts-jest/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "ts-jest/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "v8-to-istanbul/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "vite/rollup": ["rollup@4.55.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.1", "@rollup/rollup-android-arm64": "4.55.1", "@rollup/rollup-darwin-arm64": "4.55.1", "@rollup/rollup-darwin-x64": "4.55.1", "@rollup/rollup-freebsd-arm64": "4.55.1", "@rollup/rollup-freebsd-x64": "4.55.1", "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", "@rollup/rollup-linux-arm-musleabihf": "4.55.1", "@rollup/rollup-linux-arm64-gnu": "4.55.1", "@rollup/rollup-linux-arm64-musl": "4.55.1", "@rollup/rollup-linux-loong64-gnu": "4.55.1", "@rollup/rollup-linux-loong64-musl": "4.55.1", "@rollup/rollup-linux-ppc64-gnu": "4.55.1", "@rollup/rollup-linux-ppc64-musl": "4.55.1", "@rollup/rollup-linux-riscv64-gnu": "4.55.1", "@rollup/rollup-linux-riscv64-musl": "4.55.1", "@rollup/rollup-linux-s390x-gnu": "4.55.1", "@rollup/rollup-linux-x64-gnu": "4.55.1", "@rollup/rollup-linux-x64-musl": "4.55.1", "@rollup/rollup-openbsd-x64": "4.55.1", "@rollup/rollup-openharmony-arm64": "4.55.1", "@rollup/rollup-win32-arm64-msvc": "4.55.1", "@rollup/rollup-win32-ia32-msvc": "4.55.1", "@rollup/rollup-win32-x64-gnu": "4.55.1", "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A=="], + + "webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + + "webpack/watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="], + + "webpack-dev-server/http-proxy-middleware": ["http-proxy-middleware@2.0.9", "", { "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", "micromatch": "^4.0.2" }, "peerDependencies": { "@types/express": "^4.17.13" }, "optionalPeers": ["@types/express"] }, "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "@angular-devkit/build-angular/autoprefixer/fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="], + + "@angular-devkit/core/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "@angular/compiler-cli/@babel/core/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@angular/compiler-cli/@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "@angular/compiler-cli/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@angular/compiler-cli/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "@inquirer/core/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + + "@jest/reporters/jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "@jest/transform/@babel/core/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@jest/transform/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@types/jsdom/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "babel-plugin-istanbul/istanbul-lib-instrument/@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], + + "babel-plugin-istanbul/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "cli-truncate/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "cli-truncate/string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "find-cache-dir/pkg-dir/find-up": ["find-up@6.3.0", "", { "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" } }, "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw=="], + + "hpack.js/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "hpack.js/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "istanbul-lib-instrument/@babel/core/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "istanbul-lib-instrument/@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "istanbul-lib-instrument/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "istanbul-lib-report/make-dir/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "jest-config/@babel/core/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "jest-config/@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "jest-config/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "jest-haste-map/jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "jest-preset-angular/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="], + + "jest-preset-angular/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="], + + "jest-preset-angular/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="], + + "jest-preset-angular/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="], + + "jest-preset-angular/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="], + + "jest-preset-angular/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="], + + "jest-preset-angular/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="], + + "jest-preset-angular/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="], + + "jest-preset-angular/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="], + + "jest-preset-angular/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="], + + "jest-preset-angular/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="], + + "jest-preset-angular/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="], + + "jest-preset-angular/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="], + + "jest-preset-angular/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="], + + "jest-preset-angular/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="], + + "jest-preset-angular/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="], + + "jest-preset-angular/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="], + + "jest-preset-angular/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="], + + "jest-preset-angular/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="], + + "jest-preset-angular/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="], + + "jest-preset-angular/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="], + + "jest-preset-angular/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="], + + "jest-preset-angular/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="], + + "jest-preset-angular/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="], + + "jest-preset-angular/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], + + "jest-runner/jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "jest-runner/source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "jest-snapshot/@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "jest-snapshot/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "jsdom/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "jsdom/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "log-update/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "log-update/slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "log-update/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "log-update/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "parse5-html-rewriting-stream/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "parse5-sax-parser/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "sass/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "serve-index/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + + "serve-index/http-errors/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="], + + "serve-index/http-errors/inherits": ["inherits@2.0.3", "", {}, "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="], + + "serve-index/http-errors/setprototypeof": ["setprototypeof@1.1.0", "", {}, "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="], + + "serve-index/http-errors/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="], + + "vite/rollup/@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="], + + "vite/rollup/@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg=="], + + "vite/rollup/@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg=="], + + "vite/rollup/@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ=="], + + "vite/rollup/@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg=="], + + "vite/rollup/@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw=="], + + "vite/rollup/@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ=="], + + "vite/rollup/@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg=="], + + "vite/rollup/@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ=="], + + "vite/rollup/@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA=="], + + "vite/rollup/@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw=="], + + "vite/rollup/@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg=="], + + "vite/rollup/@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg=="], + + "vite/rollup/@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w=="], + + "vite/rollup/@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g=="], + + "vite/rollup/@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA=="], + + "vite/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw=="], + + "webpack/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "babel-plugin-istanbul/istanbul-lib-instrument/@babel/core/@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "babel-plugin-istanbul/istanbul-lib-instrument/@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "find-cache-dir/pkg-dir/find-up/locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], + + "find-cache-dir/pkg-dir/find-up/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], + + "log-update/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "find-cache-dir/pkg-dir/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], + + "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "find-cache-dir/pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], + + "find-cache-dir/pkg-dir/find-up/locate-path/p-locate/p-limit/yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e172f25 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +services: + tracker: + image: registry.zea.lt/miczek/tracker:latest + restart: unless-stopped + ports: + - "4000:4000" + env_file: + - .env + environment: + - NODE_ENV=production diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..ec74954 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,38 @@ +module.exports = { + preset: 'jest-preset-angular', + passWithNoTests: true, + setupFilesAfterEnv: ['/setup-jest.ts'], + globalSetup: 'jest-preset-angular/global-setup', + testMatch: ['**/*.spec.ts'], + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.spec.ts', + '!src/**/*.d.ts', + '!src/main.ts', + '!src/main.server.ts', + ], + coverageThreshold: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80, + }, + }, +}; + + + + + + + + + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..2d06c70 --- /dev/null +++ b/package.json @@ -0,0 +1,57 @@ +{ + "name": "goals-tracker", + "version": "1.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "build:prod": "bun run scripts/build-with-env.js", + "watch": "ng build --watch --configuration development", + "test": "jest", + "serve:ssr": "node scripts/serve-ssr.js", + "build:ssr": "bun run build:prod && ng run goals-tracker:server:production", + "prerender": "ng run goals-tracker:prerender" + }, + "private": true, + "dependencies": { + "dotenv": "^16.4.5", + "@angular/animations": "^19.0.0", + "@angular/common": "^19.0.0", + "@angular/compiler": "^19.2.18", + "@angular/compiler-cli": "^19.2.18", + "@angular/core": "^19.2.18", + "@angular/platform-browser": "^19.0.0", + "@angular/platform-browser-dynamic": "^19.0.0", + "@angular/platform-server": "^19.0.0", + "@angular/router": "^19.0.0", + "@angular/ssr": "^19.0.0", + "@types/three": "^0.182.0", + "chart.js": "^4.4.0", + "chartjs-plugin-datalabels": "^2.2.0", + "chartjs-plugin-zoom": "^2.2.0", + "express": "^4.18.2", + "ng2-charts": "^5.0.0", + "rxjs": "~7.8.0", + "three": "^0.182.0", + "tslib": "^2.3.0", + "zone.js": "~0.14.3" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^19.2.19", + "@angular-eslint/builder": "^19.0.0", + "@angular-eslint/eslint-plugin": "^19.0.0", + "@angular-eslint/eslint-plugin-template": "^19.0.0", + "@angular-eslint/schematics": "^19.0.0", + "@angular-eslint/template-parser": "^19.0.0", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.0", + "@types/node": "^20.10.0", + "autoprefixer": "^10.4.16", + "eslint": "^8.57.0", + "jest": "^29.7.0", + "jest-preset-angular": "^13.1.0", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0", + "typescript": "~5.8.0" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..a98689c --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,22 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} + + + + + + + + + + + + + + + + diff --git a/scripts/build-with-env.js b/scripts/build-with-env.js new file mode 100644 index 0000000..eb76f6f --- /dev/null +++ b/scripts/build-with-env.js @@ -0,0 +1,8 @@ +#!/usr/bin/env node +/** + * Loads .env and runs ng build. + * Use for production build so process.env is populated for environment.prod.ts. + */ +require('dotenv').config(); +const { execSync } = require('child_process'); +execSync('ng build', { stdio: 'inherit' }); diff --git a/scripts/serve-ssr.js b/scripts/serve-ssr.js new file mode 100644 index 0000000..5aa08e2 --- /dev/null +++ b/scripts/serve-ssr.js @@ -0,0 +1,24 @@ +#!/usr/bin/env node +/** + * Serves the SSR server by running the built main.js. + * Angular 19 server build outputs main..js. + */ +const { readdirSync } = require('fs'); +const { join } = require('path'); +const { spawn } = require('child_process'); + +const serverDir = join(process.cwd(), 'dist', 'goals-tracker', 'server'); +const files = readdirSync(serverDir); +const mainFile = files.find((f) => f.startsWith('main.') && f.endsWith('.js')); + +if (!mainFile) { + console.error('Server build not found. Run: bun run build:ssr'); + process.exit(1); +} + +const child = spawn(process.execPath, [join(serverDir, mainFile)], { + stdio: 'inherit', + env: { ...process.env, NODE_ENV: 'production' }, +}); + +child.on('exit', (code) => process.exit(code ?? 0)); diff --git a/server.ts b/server.ts new file mode 100644 index 0000000..377947a --- /dev/null +++ b/server.ts @@ -0,0 +1,265 @@ +import 'dotenv/config'; +import 'zone.js/node'; +import { APP_BASE_HREF } from '@angular/common'; +import { CommonEngine } from '@angular/ssr/node'; +import express from 'express'; +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import AppServerModule from './src/main.server'; + +const STRAVA_API_BASE = 'https://www.strava.com/api/v3'; +const FACEIT_API_BASE = 'https://open.faceit.com'; +const TRACKER_API_BASE = 'https://public-api.tracker.gg'; + +function getRiotApiKey(path: string, gameTypeHeader: string | undefined): string { + const lolKey = process.env['RIOT_API_KEY_LOL'] || ''; + const tftKey = process.env['RIOT_API_KEY_TFT'] || ''; + if (gameTypeHeader === 'tft' || path.includes('/tft/')) { + return tftKey; + } + return lolKey; +} + +// The Express app is exported so that it can be used by serverless Functions. +export function app(): express.Express { + const server = express(); + const distFolder = join(process.cwd(), 'dist/goals-tracker/browser'); + const indexHtml = existsSync(join(distFolder, 'index.original.html')) + ? join(distFolder, 'index.original.html') + : join(distFolder, 'index.html'); + + const commonEngine = new CommonEngine(); + + server.set('view engine', 'html'); + server.set('views', distFolder); + + server.use(express.json()); + server.use(express.urlencoded({ extended: true })); + + // --- API Proxy routes (before static and catch-all) --- + + server.post('/api/auth/strava/token', async (req, res) => { + const { code } = req.body || {}; + const clientId = process.env['STRAVA_CLIENT_ID']; + const clientSecret = process.env['STRAVA_CLIENT_SECRET']; + + if (!code || !clientId || !clientSecret) { + res.status(400).json({ error: 'Missing code or Strava credentials' }); + return; + } + + try { + const params = new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + code, + grant_type: 'authorization_code', + }); + const response = await fetch(`${STRAVA_API_BASE}/oauth/token`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params.toString(), + }); + const data = await response.json(); + res.status(response.status).json(data); + } catch (err) { + res.status(500).json({ error: 'Token exchange failed' }); + } + }); + + server.post('/api/auth/strava/refresh', async (req, res) => { + const { refresh_token } = req.body || {}; + const clientId = process.env['STRAVA_CLIENT_ID']; + const clientSecret = process.env['STRAVA_CLIENT_SECRET']; + + if (!refresh_token || !clientId || !clientSecret) { + res.status(400).json({ error: 'Missing refresh_token or Strava credentials' }); + return; + } + + try { + const params = new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + refresh_token, + grant_type: 'refresh_token', + }); + const response = await fetch(`${STRAVA_API_BASE}/oauth/token`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params.toString(), + }); + const data = await response.json(); + res.status(response.status).json(data); + } catch (err) { + res.status(500).json({ error: 'Token refresh failed' }); + } + }); + + server.all(/^\/api\/proxy\/riot\//, async (req, res) => { + const match = req.path.match(/^\/api\/proxy\/riot\/([^/]+)\/(.*)$/); + const routing = match?.[1] ?? ''; + const rest = (match?.[2] ?? '').replace(/^\//, ''); + const gameType = req.headers['x-riot-game-type'] as string | undefined; + const apiKey = getRiotApiKey(rest, gameType); + + if (!apiKey) { + res.status(502).json({ error: 'Riot API key not configured' }); + return; + } + + const targetUrl = `https://${routing}.api.riotgames.com/${rest}`; + const query = req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : ''; + + try { + const headers: Record = { + 'X-Riot-Token': apiKey.trim(), + }; + const fetchOpts: RequestInit = { + method: req.method, + headers, + }; + if (req.method !== 'GET' && req.body && Object.keys(req.body).length > 0) { + headers['Content-Type'] = 'application/json'; + fetchOpts.body = JSON.stringify(req.body); + } + + const response = await fetch(targetUrl + query, fetchOpts); + const text = await response.text(); + res.status(response.status); + const contentType = response.headers.get('content-type'); + if (contentType?.includes('application/json')) { + res.json(JSON.parse(text || '{}')); + } else { + res.send(text); + } + } catch (err) { + res.status(502).json({ error: 'Riot proxy failed' }); + } + }); + + server.all(/^\/api\/proxy\/faceit/, async (req, res) => { + const path = req.path.replace(/^\/api\/proxy\/faceit/, '') || '/'; + const apiKey = process.env['FACEIT_API_KEY']; + + if (!apiKey) { + res.status(502).json({ error: 'Faceit API key not configured' }); + return; + } + + const targetUrl = `${FACEIT_API_BASE}${path}`; + const query = req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : ''; + + try { + const headers: Record = { + Authorization: `Bearer ${apiKey}`, + }; + const fetchOpts: RequestInit = { + method: req.method, + headers, + }; + if (req.method !== 'GET' && req.body && Object.keys(req.body).length > 0) { + headers['Content-Type'] = 'application/json'; + fetchOpts.body = JSON.stringify(req.body); + } + + const response = await fetch(targetUrl + query, fetchOpts); + const text = await response.text(); + res.status(response.status); + const contentType = response.headers.get('content-type'); + if (contentType?.includes('application/json')) { + res.json(JSON.parse(text || '{}')); + } else { + res.send(text); + } + } catch (err) { + res.status(502).json({ error: 'Faceit proxy failed' }); + } + }); + + server.all(/^\/api\/proxy\/tracker/, async (req, res) => { + const path = req.path.replace(/^\/api\/proxy\/tracker/, '') || '/'; + const apiKey = process.env['TRACKER_GG_API_KEY']; + + if (!apiKey) { + res.status(502).json({ error: 'Tracker.gg API key not configured' }); + return; + } + + const targetUrl = `${TRACKER_API_BASE}${path}`; + const query = req.url.includes('?') ? req.url.substring(req.url.indexOf('?')) : ''; + + try { + const headers: Record = { + 'TRN-Api-Key': apiKey, + }; + const fetchOpts: RequestInit = { + method: req.method, + headers, + }; + if (req.method !== 'GET' && req.body && Object.keys(req.body).length > 0) { + headers['Content-Type'] = 'application/json'; + fetchOpts.body = JSON.stringify(req.body); + } + + const response = await fetch(targetUrl + query, fetchOpts); + const text = await response.text(); + res.status(response.status); + const contentType = response.headers.get('content-type'); + if (contentType?.includes('application/json')) { + res.json(JSON.parse(text || '{}')); + } else { + res.send(text); + } + } catch (err) { + res.status(502).json({ error: 'Tracker.gg proxy failed' }); + } + }); + + // Serve static files from /browser + server.get( + '*.*', + express.static(distFolder, { + maxAge: '1y', + }) + ); + + // All regular routes use the Angular engine + server.get('*', (req, res, next) => { + const { protocol, originalUrl, baseUrl, headers } = req; + + commonEngine + .render({ + bootstrap: AppServerModule.bootstrap, + providers: [ + ...AppServerModule.providers, + { provide: APP_BASE_HREF, useValue: baseUrl }, + ] as unknown as import('@angular/core').StaticProvider[], + documentFilePath: indexHtml, + url: `${protocol}://${headers.host}${originalUrl}`, + publicPath: distFolder, + }) + .then((html: string) => res.send(html)) + .catch((err: unknown) => next(err)); + }); + + return server; +} + +function run(): void { + const port = process.env['PORT'] || 4000; + + const server = app(); + server.listen(port, () => { + console.log(`Node Express server listening on http://localhost:${port}`); + }); +} + +declare const __non_webpack_require__: NodeRequire; +const mainModule = __non_webpack_require__.main; +const moduleFilename = (mainModule && mainModule.filename) || ''; +if (moduleFilename === __filename || moduleFilename.includes('iisnode')) { + run(); +} + +export default AppServerModule.bootstrap; diff --git a/setup-jest.ts b/setup-jest.ts new file mode 100644 index 0000000..ef26278 --- /dev/null +++ b/setup-jest.ts @@ -0,0 +1,17 @@ +import 'jest-preset-angular/setup-jest'; + + + + + + + + + + + + + + + + diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100644 index 0000000..ad89bad --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,135 @@ +import { Component, OnInit, signal, effect, inject, computed } from '@angular/core'; +import { RouterOutlet, NavigationEnd, Router } from '@angular/router'; +import { ParticleBackgroundComponent } from './shared/components/particle-background/particle-background.component'; +import { CustomCursorComponent } from './shared/components/custom-cursor/custom-cursor.component'; +import { LoadingScreenComponent } from './shared/components/loading-screen/loading-screen.component'; +import { NavbarComponent } from './shared/components/navbar/navbar.component'; +import { FooterComponent } from './shared/components/footer/footer.component'; +import { GoalsService } from './core/services/goals.service'; +import { filter, map, startWith } from 'rxjs/operators'; +import { toSignal } from '@angular/core/rxjs-interop'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [ + RouterOutlet, + ParticleBackgroundComponent, + CustomCursorComponent, + LoadingScreenComponent, + NavbarComponent, + FooterComponent, + ], + template: ` + + +
+ +
+ @if (showNavbarAndFooter()) { + + } +
+ +
+ @if (showNavbarAndFooter()) { + + } +
+
+ `, + styles: [], +}) +export class AppComponent implements OnInit { + title = 'goals-tracker'; + loadingProgress = signal(0); + loadingText = signal('Initializing...'); + loadingComplete = signal(false); + + private readonly goalsService = inject(GoalsService); + private readonly router = inject(Router); + + private readonly currentUrl = toSignal( + this.router.events.pipe( + filter((event) => event instanceof NavigationEnd), + map((event) => (event as NavigationEnd).url), + startWith(this.router.url) + ), + { initialValue: this.router.url } + ); + + readonly showNavbarAndFooter = computed(() => { + const url = this.currentUrl(); + // Hide navbar and footer on dashboard (root path) + return url !== '/' && !url.startsWith('/?'); + }); + + constructor() { + // Track loading progress and text from service + effect(() => { + const serviceProgress = this.goalsService.loadingProgress$(); + const serviceText = this.goalsService.loadingText$(); + if (serviceProgress > 0) { + // Add 5% for initial setup, service progress goes 0-95% + this.loadingProgress.set(Math.min(95, 5 + serviceProgress)); + } + if (serviceText) { + this.loadingText.set(serviceText); + } + }); + + effect(() => { + if (this.loadingComplete()) { + // Hide loading screen after fade-out animation + setTimeout(() => { + // Component will be hidden via CSS + }, 500); + } + }); + } + + ngOnInit(): void { + this.loadAllData(); + + // Track route changes for navigation loading + this.router.events + .pipe(filter((event) => event instanceof NavigationEnd)) + .subscribe(() => { + // Data is cached, no need to reload + }); + } + + private loadAllData(): void { + this.loadingText.set('Initializing...'); + this.loadingProgress.set(5); + + // Small delay to show initial state + setTimeout(() => { + this.goalsService.preloadAllData().subscribe({ + next: () => { + this.loadingText.set('Complete!'); + this.loadingProgress.set(100); + setTimeout(() => { + this.loadingComplete.set(true); + }, 300); + }, + error: () => { + // Even on error, show the app + this.loadingText.set('Ready'); + this.loadingProgress.set(100); + setTimeout(() => { + this.loadingComplete.set(true); + }, 300); + }, + }); + }, 200); + } +} + diff --git a/src/app/app.config.ts b/src/app/app.config.ts new file mode 100644 index 0000000..e36b9e1 --- /dev/null +++ b/src/app/app.config.ts @@ -0,0 +1,14 @@ +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { routes } from './app.routes'; +import { apiInterceptor } from './core/interceptors/api.interceptor'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideHttpClient(withInterceptors([apiInterceptor])), + ], +}; + diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts new file mode 100644 index 0000000..6b775b1 --- /dev/null +++ b/src/app/app.routes.ts @@ -0,0 +1,33 @@ +import { Routes } from '@angular/router'; + +export const routes: Routes = [ + { + path: '', + loadComponent: () => + import('./features/dashboard/dashboard.component').then( + (m) => m.DashboardComponent + ), + }, + { + path: 'sport', + loadComponent: () => + import('./features/sport/sport-goals.component').then( + (m) => m.SportGoalsComponent + ), + }, + { + path: 'gaming', + loadComponent: () => + import('./features/gaming/gaming-goals.component').then( + (m) => m.GamingGoalsComponent + ), + }, + { + path: 'auth/strava/callback', + loadComponent: () => + import('./features/sport/strava-callback.component').then( + (m) => m.StravaCallbackComponent + ), + }, +]; + diff --git a/src/app/app.server.ts b/src/app/app.server.ts new file mode 100644 index 0000000..bb264b8 --- /dev/null +++ b/src/app/app.server.ts @@ -0,0 +1,12 @@ +import { provideServerRendering } from '@angular/platform-server'; +import { AppComponent } from './app.component'; +import { appConfig } from './app.config'; + +export const AppServerModule = { + bootstrap: AppComponent, + providers: [ + ...appConfig.providers, + provideServerRendering(), + ], +}; + diff --git a/src/app/core/interceptors/api.interceptor.ts b/src/app/core/interceptors/api.interceptor.ts new file mode 100644 index 0000000..29ba0c1 --- /dev/null +++ b/src/app/core/interceptors/api.interceptor.ts @@ -0,0 +1,57 @@ +import { HttpInterceptorFn, HttpRequest } from '@angular/common/http'; + +let requestCount = 0; +const requestSummary: Record = {}; + +if (typeof window !== 'undefined') { + window.addEventListener('beforeunload', () => { + console.log('=== API Request Summary ==='); + Object.entries(requestSummary).forEach(([api, count]) => { + console.log(`${api}: ${count} requests`); + }); + console.log(`Total: ${requestCount} requests`); + }); + + setTimeout(() => { + console.log('=== API Request Summary (5s) ==='); + Object.entries(requestSummary).forEach(([api, count]) => { + console.log(`${api}: ${count} requests`); + }); + console.log(`Total: ${requestCount} requests`); + }, 5000); +} + +function getApiType(req: HttpRequest): string { + const url = req.url; + if (url.includes('/api/auth/strava') || url.includes('strava.com')) { + return 'Strava'; + } + if (url.includes('/api/proxy/riot') || url.includes('api.riotgames.com')) { + return 'Riot'; + } + if (url.includes('/api/proxy/faceit') || url.includes('open.faceit.com')) { + return 'Faceit'; + } + if (url.includes('/api/proxy/tracker') || url.includes('tracker.gg')) { + return 'Tracker.gg'; + } + return 'Unknown'; +} + +function logRequest(req: HttpRequest, apiType: string): void { + requestCount++; + requestSummary[apiType] = (requestSummary[apiType] || 0) + 1; + console.log( + `[API Request #${requestCount}] ${apiType} | ${req.method} ${req.url}` + ); +} + +/** + * API interceptor - pass through all requests (auth is handled by backend proxy). + * Optionally logs request summary. + */ +export const apiInterceptor: HttpInterceptorFn = (req, next) => { + const apiType = getApiType(req); + logRequest(req, apiType); + return next(req); +}; diff --git a/src/app/core/models/api-response.model.ts b/src/app/core/models/api-response.model.ts new file mode 100644 index 0000000..a02f590 --- /dev/null +++ b/src/app/core/models/api-response.model.ts @@ -0,0 +1,304 @@ +// Strava API Responses +export interface StravaActivity { + id: number; + name: string; + type: string; + distance: number; // meters + start_date: string; + moving_time: number; // seconds + sport_type?: string; +} + +export interface StravaTokenResponse { + access_token: string; + refresh_token: string; + expires_at: number; + athlete: { + id: number; + }; +} + +// Riot API Responses +export interface RiotAccount { + puuid: string; + gameName: string; + tagLine: string; +} + +export interface RiotSummoner { + id: string; + accountId: string; + puuid: string; + name: string; + profileIconId: number; + revisionDate: number; + summonerLevel: number; +} + +export interface RiotLeagueEntry { + leagueId: string; + summonerId: string; + summonerName: string; + queueType: string; + tier: string; + rank: string; + leaguePoints: number; + wins: number; + losses: number; + veteran: boolean; + inactive: boolean; + freshBlood: boolean; + hotStreak: boolean; +} + +export interface RiotMatch { + metadata: { + matchId: string; + participants: string[]; + }; + info: { + gameCreation: number; + gameDuration: number; + gameEndTimestamp: number; + participants: Array<{ + puuid: string; + teamId: number; + win: boolean; + championName: string; + kills: number; + deaths: number; + assists: number; + }>; + teams: Array<{ + teamId: number; + win: boolean; + }>; + }; +} + +export interface TFTMatch { + metadata: { + data_version: string; + match_id: string; + participants: string[]; + }; + info: { + game_datetime: number; + game_length: number; + game_version: string; + participants: Array<{ + puuid: string; + placement: number; + level: number; + gold_left: number; + last_round: number; + time_eliminated: number; + total_damage_to_players: number; + traits: Array<{ + name: string; + num_units: number; + style: number; + tier_current: number; + tier_total: number; + }>; + units: Array<{ + character_id: string; + tier: number; + items: number[]; + }>; + }>; + queue_id: number; + tft_game_type: string; + tft_set_core_name: string; + tft_set_number: number; + }; +} + +// Faceit API Responses +export interface FaceitPlayerSearch { + items: Array<{ + player_id: string; + nickname: string; + avatar: string; + country: string; + }>; +} + +export interface FaceitPlayer { + player_id: string; + nickname: string; + avatar: string; + country: string; + cover_image: string; + cover_featured_image: string; + infractions: unknown; + verified: boolean; + faceit_url: string; + membership_type: string; + membership_subscriptions: unknown[]; + games: { + [key: string]: { + game_profile_id: string; + region: string; + regions: string[]; + skill_level: number; + faceit_elo: number; + game_player_id: string; + game_player_name: string; + skill_level_label: string; + regions_object: unknown[]; + game_regions: unknown[]; + }; + }; + friends_ids: string[]; + bans: unknown[]; + new_steam_id: string; + steam_id_64: string; + steam_nickname: string; + memberships: string[]; + faceit_elo: number; + created_at: number; + email: string; +} + +export interface FaceitMatch { + match_id: string; + game_id: string; + region: string; + match_type: string; + game_mode: string; + max_players: number; + teams_size: number; + teams: { + faction1: { + team_id: string; + nickname: string; + avatar: string; + type: string; + players: Array<{ + player_id: string; + nickname: string; + avatar: string; + skill_level: number; + game_player_id: string; + game_player_name: string; + faceit_elo: number; + }>; + }; + faction2: { + team_id: string; + nickname: string; + avatar: string; + type: string; + players: Array<{ + player_id: string; + nickname: string; + avatar: string; + skill_level: number; + game_player_id: string; + game_player_name: string; + faceit_elo: number; + }>; + }; + }; + playing_players: string[]; + competition_id: string; + competition_name: string; + competition_type: string; + organizer_id: string; + status: string; + started_at: number; + finished_at: number; + results: { + winner: string; + score: { + faction1: number; + faction2: number; + }; + }; +} + +export interface FaceitMatchStats { + rounds: Array<{ + best_of: string; + competition_id: string; + game_id: string; + game_mode: string; + match_id: string; + match_round: string; + played: string; + round_stats: Record; + teams: Array<{ + team_id: string; + premade: boolean; + team_stats: Record; + players: Array<{ + player_id: string; + nickname: string; + player_stats: Record; + }>; + }>; + }>; +} + +// Tracker.gg API Responses +export interface TrackerGGProfile { + data: { + platformInfo: { + platformSlug: string; + platformUserId: string; + platformUserHandle: string; + platformUserIdentifier: string; + avatarUrl: string; + additionalParameters: unknown; + }; + userInfo: { + userId: string; + isPremium: boolean; + isVerified: boolean; + isInfluencer: boolean; + isPartner: boolean; + countryCode: string; + customAvatarUrl: string; + customHeroUrl: string; + socialAccounts: unknown[]; + pageviews: number; + isSuspicious: boolean; + }; + metadata: { + lastUpdated: { + value: string; + displayValue: string; + }; + }; + segments: Array<{ + type: string; + attributes: { + playlistId: string; + playlistName: string; + rank: { + metadata: { + iconUrl: string; + rankName: string; + tierName: string; + tier: number; + }; + value: number; + displayValue: string; + }; + rating: { + value: number; + displayValue: string; + }; + }; + metadata: { + name: string; + }; + expiryDate: string; + stats: unknown; + }>; + availableSegments: unknown[]; + expiryDate: string; + }; +} + diff --git a/src/app/core/models/gaming-goal.model.ts b/src/app/core/models/gaming-goal.model.ts new file mode 100644 index 0000000..475ec68 --- /dev/null +++ b/src/app/core/models/gaming-goal.model.ts @@ -0,0 +1,74 @@ +export type GameType = 'tft' | 'lol' | 'rocket-league' | 'faceit'; + +export interface Match { + id: string; + game: GameType; + date: Date; + result: 'win' | 'loss' | 'draw'; + rank?: string; + lp?: number; + lpChange?: number; + score?: string; + opponent?: string; + champion?: string; + kda?: string; + placement?: number; // For TFT (1-8) + duration?: number; // Game duration in seconds + map?: string; + matchUrl?: string; // Link to match details on external site + isPromotion?: boolean; // Ranked up after this match + isDemotion?: boolean; // Ranked down after this match + newRank?: string; // The new rank after promotion/demotion +} + +export interface RankInfo { + tier: string; + rank?: string; + leaguePoints: number; + wins: number; + losses: number; + hotStreak?: boolean; + veteran?: boolean; + freshBlood?: boolean; +} + +export interface Streak { + type: 'win' | 'loss' | 'none'; + count: number; +} + +export interface GamingStats { + winRate: number; + totalGames: number; + recentWins: number; + recentLosses: number; + streak: Streak; + elo?: number; + peakRank?: string; + avgPlacement?: number; // For TFT + kda?: number; // KDA ratio (for Faceit) + adr?: number; // Average Damage per Round (for Faceit) +} + +export interface GamingGoal { + game: GameType; + target: string; // 'Diamond', 'Champion', 'Level 10' + current: string; + progress: number; // 0-100 + recentMatches: Match[]; + rankInfo?: RankInfo; + stats?: GamingStats; +} + +export interface GamingProgress { + tft: GamingGoal; + lol: GamingGoal; + rocketLeague: GamingGoal; + faceit: GamingGoal; +} + + + + + + diff --git a/src/app/core/models/sport-goal.model.ts b/src/app/core/models/sport-goal.model.ts new file mode 100644 index 0000000..c4da718 --- /dev/null +++ b/src/app/core/models/sport-goal.model.ts @@ -0,0 +1,41 @@ +export type SportType = 'bike' | 'run' | 'swim'; + +export interface Activity { + id: number; + name: string; + type: SportType; + distance: number; // meters + startDate: Date; + movingTime: number; // seconds +} + +export interface SportGoal { + type: SportType; + target: number; // km + current: number; // km + percentage: number; + activities: Activity[]; +} + +export interface SportProgress { + bike: SportGoal; + run: SportGoal; + swim: SportGoal; + overallPercentage: number; +} + + + + + + + + + + + + + + + + diff --git a/src/app/core/services/faceit.service.ts b/src/app/core/services/faceit.service.ts new file mode 100644 index 0000000..dc27ccc --- /dev/null +++ b/src/app/core/services/faceit.service.ts @@ -0,0 +1,550 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of, forkJoin, from } from 'rxjs'; +import { map, catchError, switchMap, concatMap, delay, toArray } from 'rxjs/operators'; +import { environment } from '../../../environments/environment'; +import { + GamingGoal, + GamingStats, + Match, + Streak, +} from '../models/gaming-goal.model'; +import { + FaceitPlayer, + FaceitMatch, + FaceitMatchStats, + FaceitPlayerSearch, +} from '../models/api-response.model'; + +const FACEIT_API_BASE = '/api/proxy/faceit/data/v4'; +const FACEIT_GAME_ID = 'cs2'; // CS2 game ID + +@Injectable({ + providedIn: 'root', +}) +export class FaceitService { + private readonly http = inject(HttpClient); + private readonly userId = environment.faceit.userId; + + /** + * Get Faceit playerId (for Load More functionality) + */ + getFaceitPlayerId(): Observable { + if (!this.userId) { + return of(null); + } + return this.searchPlayer(this.userId).pipe( + catchError(() => of(null)) + ); + } + + /** + * Get Faceit level and progress with full stats + */ + getFaceitProgress(): Observable { + if (!this.userId) { + return of(this.getEmptyGoal()); + } + + // First search for player by nickname to get player_id + return this.searchPlayer(this.userId).pipe( + switchMap((playerId) => { + if (!playerId) { + console.warn('Faceit player not found. Returning empty goal.'); + return of(this.getEmptyGoal()); + } + // Fetch player info and match history in parallel + return forkJoin({ + player: this.getPlayerById(playerId), + matches: this.getPlayerMatches(playerId), + stats: this.getPlayerStats(playerId), + }).pipe( + map(({ player, matches, stats }) => { + const gameData = player.games[FACEIT_GAME_ID]; + const level = gameData?.skill_level || 0; + const currentElo = gameData?.faceit_elo || 0; + const current = `Level ${level}`; + // Calculate progress based on ELO (Level 10 = 2001+ ELO) + const progress = this.calculateEloProgress(currentElo, 2001); + + // Calculate rank changes for each match based on ELO + const matchesWithRankChanges = this.calculateMatchRankChanges( + matches, + currentElo + ); + + // Calculate streak from recent matches + const streak = this.calculateStreak(matchesWithRankChanges); + const recentWins = matchesWithRankChanges.filter( + (m) => m.result === 'win' + ).length; + const recentLosses = matchesWithRankChanges.filter( + (m) => m.result === 'loss' + ).length; + + const gamingStats: GamingStats = { + winRate: stats?.winRate ?? 0, + totalGames: stats?.totalGames ?? 0, + recentWins, + recentLosses, + streak, + elo: currentElo, + kda: stats?.kda, + adr: stats?.adr, + }; + + const goal: GamingGoal = { + game: 'faceit', + target: 'Level 10', + current, + progress, + recentMatches: matchesWithRankChanges, + stats: gamingStats, + }; + return goal; + }) + ); + }), + catchError((error) => { + console.error('Error fetching Faceit progress:', error); + if (error.status === 404) { + console.warn( + 'Faceit player not found. Verify your Faceit username. ' + + 'Find it in your profile URL: https://www.faceit.com/en/players/YOUR_USERNAME' + ); + } else if (error.status === 401 || error.status === 403) { + console.warn( + 'Faceit API authentication failed. Verify your API key at: ' + + 'https://developers.faceit.com/' + ); + } + return of(this.getEmptyGoal()); + }) + ); + } + + /** + * Get recent matches (public method) + */ + getRecentMatches(): Observable { + if (!this.userId) { + return of([]); + } + + return this.searchPlayer(this.userId).pipe( + switchMap((playerId) => { + if (!playerId) { + return of([]); + } + return this.getPlayerMatches(playerId); + }), + catchError(() => of([])) + ); + } + + /** + * Get player's match history by player ID with ELO changes + */ + private getPlayerMatches(playerId: string): Observable { + return this.http + .get<{ items: FaceitMatch[] }>( + `${FACEIT_API_BASE}/players/${playerId}/history`, + { params: { game: FACEIT_GAME_ID, limit: '5' } } + ) + .pipe( + switchMap((response) => { + const matches = response.items || []; + if (matches.length === 0) { + return of([]); + } + + // Fetch match stats in batches of 5 to avoid rate limiting + const batchSize = 5; + const batches: typeof matches[] = []; + + for (let i = 0; i < matches.length; i += batchSize) { + batches.push(matches.slice(i, i + batchSize)); + } + + // Process batches sequentially with delay between batches + return from(batches).pipe( + concatMap((batch, index) => { + const batchRequests = batch.map((match) => + this.getMatchStats(match.match_id, playerId).pipe( + map((eloChange) => this.mapToMatch(match, playerId, eloChange)), + catchError(() => of(this.mapToMatch(match, playerId, null))) + ) + ); + + return forkJoin(batchRequests).pipe( + // Add delay between batches (except first one) + delay(index === 0 ? 0 : 200) + ); + }), + // Collect all batches into a single array + toArray(), + map((allBatches) => { + // Flatten array of arrays + return allBatches.flat(); + }) + ); + }), + catchError(() => of([])) + ); + } + + /** + * Get more Faceit matches (for Load More functionality) + */ + getMoreFaceitMatches(playerId: string, startIndex: number, count: number = 5): Observable { + if (!playerId) { + return of([]); + } + return this.http + .get<{ items: FaceitMatch[] }>( + `${FACEIT_API_BASE}/players/${playerId}/history`, + { + params: { + game: FACEIT_GAME_ID, + limit: count.toString(), + offset: startIndex.toString(), + }, + } + ) + .pipe( + switchMap((response) => { + const matches = response.items || []; + if (matches.length === 0) { + return of([]); + } + + // Fetch match stats in batches of 5 to avoid rate limiting + const batchSize = 5; + const batches: typeof matches[] = []; + + for (let i = 0; i < matches.length; i += batchSize) { + batches.push(matches.slice(i, i + batchSize)); + } + + // Process batches sequentially with delay between batches + return from(batches).pipe( + concatMap((batch, index) => { + const batchRequests = batch.map((match) => + this.getMatchStats(match.match_id, playerId).pipe( + map((eloChange) => this.mapToMatch(match, playerId, eloChange)), + catchError(() => of(this.mapToMatch(match, playerId, null))) + ) + ); + + return forkJoin(batchRequests).pipe( + // Add delay between batches (except first one) + delay(index === 0 ? 0 : 200) + ); + }), + // Collect all batches into a single array + toArray(), + map((allBatches) => { + // Flatten array of arrays + return allBatches.flat(); + }) + ); + }), + catchError((error) => { + console.error('Error fetching more Faceit matches:', error); + return of([]); + }) + ); + } + + /** + * Get match stats to extract ELO change for player + */ + private getMatchStats( + matchId: string, + playerId: string + ): Observable { + return this.http + .get(`${FACEIT_API_BASE}/matches/${matchId}/stats`) + .pipe( + map((stats) => { + // Find player in the stats to get ELO change + for (const round of stats.rounds || []) { + for (const team of round.teams || []) { + const player = team.players?.find( + (p) => p.player_id === playerId + ); + if (player?.player_stats?.['Elo']) { + // Some stats show current ELO, not change + // Try to find elo_change if available + const eloChange = player.player_stats?.['Elo Change']; + if (eloChange) { + return parseInt(eloChange, 10); + } + } + } + } + return null; + }), + catchError(() => of(null)) + ); + } + + /** + * Get player's stats + */ + private getPlayerStats( + playerId: string + ): Observable<{ winRate: number; totalGames: number; kda: number; adr: number } | null> { + return this.http + .get<{ + lifetime: { + Matches: string; + 'Win Rate %': string; + Wins: string; + 'Current Win Streak': string; + 'Longest Win Streak': string; + 'Average K/D Ratio': string; + 'Average Damage per Round': string; + }; + }>(`${FACEIT_API_BASE}/players/${playerId}/stats/${FACEIT_GAME_ID}`) + .pipe( + map((response) => { + const lifetime = response.lifetime; + return { + winRate: parseFloat(lifetime['Win Rate %']) || 0, + totalGames: parseInt(lifetime.Matches, 10) || 0, + kda: parseFloat(lifetime['Average K/D Ratio']) || 0, + adr: parseFloat(lifetime['Average Damage per Round']) || 0, + }; + }), + catchError(() => of(null)) + ); + } + + /** + * Calculate current streak from matches + */ + private calculateStreak(matches: Match[]): Streak { + if (matches.length === 0) { + return { type: 'none', count: 0 }; + } + + const firstResult = matches[0].result; + if (firstResult === 'draw') { + return { type: 'none', count: 0 }; + } + + let count = 0; + for (const match of matches) { + if (match.result === firstResult) { + count++; + } else { + break; + } + } + + return { type: firstResult, count }; + } + + /** + * Search for player by nickname to get player_id + */ + private searchPlayer(nickname: string): Observable { + return this.http + .get(`${FACEIT_API_BASE}/search/players`, { + params: { + nickname: nickname, + game: FACEIT_GAME_ID, + limit: '1', + }, + }) + .pipe( + map((response) => { + if (response.items && response.items.length > 0) { + // Find exact match (case-insensitive) + const exactMatch = response.items.find( + (item) => + item.nickname.toLowerCase() === nickname.toLowerCase() + ); + return exactMatch?.player_id || response.items[0].player_id; + } + return null; + }), + catchError((error) => { + console.error('Error searching for Faceit player:', error); + return of(null); + }) + ); + } + + /** + * Get player by player_id + */ + private getPlayerById(playerId: string): Observable { + return this.http.get( + `${FACEIT_API_BASE}/players/${playerId}` + ); + } + + private mapToMatch( + match: FaceitMatch, + playerId: string, + eloChange: number | null = null + ): Match { + // Determine which faction the player is on + let result: 'win' | 'loss' | 'draw' = 'draw'; + let playerFaction: 'faction1' | 'faction2' | null = null; + + if (match.teams?.faction1?.players?.some((p) => p.player_id === playerId)) { + playerFaction = 'faction1'; + } else if ( + match.teams?.faction2?.players?.some((p) => p.player_id === playerId) + ) { + playerFaction = 'faction2'; + } + + if (playerFaction && match.results?.winner) { + result = match.results.winner === playerFaction ? 'win' : 'loss'; + } + + const score = match.results?.score + ? `${match.results.score.faction1}-${match.results.score.faction2}` + : undefined; + + // Faceit has direct match room URLs + const matchUrl = `https://www.faceit.com/en/cs2/room/${match.match_id}`; + + // Only include ELO change if we have real data from the API + return { + id: match.match_id, + game: 'faceit', + date: new Date(match.started_at * 1000), + result, + score, + map: match.game_mode, + duration: match.finished_at + ? match.finished_at - match.started_at + : undefined, + matchUrl, + lpChange: eloChange ?? undefined, + }; + } + + /** + * Faceit ELO thresholds for each level + * Source: https://support.faceit.com/hc/en-us/articles/208511105-Skill-Level-and-ELO + */ + private readonly eloThresholds: number[] = [ + 0, // Level 1: 1-500 + 501, // Level 2: 501-750 + 751, // Level 3: 751-900 + 901, // Level 4: 901-1050 + 1051, // Level 5: 1051-1200 + 1201, // Level 6: 1201-1350 + 1351, // Level 7: 1351-1530 + 1531, // Level 8: 1531-1750 + 1751, // Level 9: 1751-2000 + 2001, // Level 10: 2001+ + ]; + + /** + * Get Faceit level from ELO + */ + private getLevelFromElo(elo: number): number { + for (let i = this.eloThresholds.length - 1; i >= 0; i--) { + if (elo >= this.eloThresholds[i]) { + return i + 1; + } + } + return 1; + } + + /** + * Check if ELO change resulted in promotion/demotion + */ + private checkRankChange( + currentElo: number, + eloChange: number + ): { isPromotion: boolean; isDemotion: boolean; newRank?: string } { + const previousElo = currentElo - eloChange; + const currentLevel = this.getLevelFromElo(currentElo); + const previousLevel = this.getLevelFromElo(previousElo); + + if (currentLevel > previousLevel) { + return { + isPromotion: true, + isDemotion: false, + newRank: `Level ${currentLevel}`, + }; + } else if (currentLevel < previousLevel) { + return { + isPromotion: false, + isDemotion: true, + newRank: `Level ${currentLevel}`, + }; + } + + return { isPromotion: false, isDemotion: false }; + } + + /** + * Calculate rank changes for each match by working backwards from current ELO + * Matches are ordered most recent first + */ + private calculateMatchRankChanges( + matches: Match[], + currentElo: number + ): Match[] { + let runningElo = currentElo; + + // Process matches from most recent to oldest + return matches.map((match) => { + const eloChange = match.lpChange ?? 0; + const eloAfterMatch = runningElo; + + // Check if this match caused a rank change + const rankChange = this.checkRankChange(eloAfterMatch, eloChange); + + // Update running ELO for next iteration (going backwards in time) + runningElo = eloAfterMatch - eloChange; + + return { + ...match, + isPromotion: rankChange.isPromotion, + isDemotion: rankChange.isDemotion, + newRank: rankChange.newRank, + }; + }); + } + + private calculateLevelProgress(current: number, target: number): number { + if (current >= target) { + return 100; + } + return (current / target) * 100; + } + + /** + * Calculate progress based on ELO (more precise than level-based) + * Target Level 10 = 2001 ELO + */ + private calculateEloProgress(currentElo: number, targetElo: number): number { + if (currentElo >= targetElo) { + return 100; + } + // Calculate progress: (current ELO / target ELO) * 100 + // This gives more granular progress than just level-based + return Math.min(100, (currentElo / targetElo) * 100); + } + + private getEmptyGoal(): GamingGoal { + return { + game: 'faceit', + target: 'Level 10', + current: 'Level 0', + progress: 0, + recentMatches: [], + }; + } +} + diff --git a/src/app/core/services/goals.service.ts b/src/app/core/services/goals.service.ts new file mode 100644 index 0000000..0d79a3c --- /dev/null +++ b/src/app/core/services/goals.service.ts @@ -0,0 +1,359 @@ +import { inject, Injectable, signal, computed } from '@angular/core'; +import { Observable, combineLatest, of, forkJoin } from 'rxjs'; +import { map, catchError, shareReplay } from 'rxjs/operators'; +import { StravaService } from './strava.service'; +import { RiotService } from './riot.service'; +import { FaceitService } from './faceit.service'; +import { TrackerGGService } from './tracker-gg.service'; +import { environment } from '../../../environments/environment'; +import { + SportProgress, + SportGoal, + SportType, +} from '../models/sport-goal.model'; +import { GamingProgress, GamingGoal } from '../models/gaming-goal.model'; +import { getIdealProgress } from '../../shared/utils/date.utils'; + +@Injectable({ + providedIn: 'root', +}) +export class GoalsService { + private readonly stravaService = inject(StravaService); + private readonly riotService = inject(RiotService); + private readonly faceitService = inject(FaceitService); + private readonly trackerService = inject(TrackerGGService); + + private readonly sportProgress = signal(null); + private readonly gamingProgress = signal(null); + private readonly isLoading = signal(false); + private readonly loadingProgress = signal(0); + private readonly loadingText = signal('Initializing...'); + + private sportGoalsRequest$: Observable | null = null; + private gamingGoalsRequest$: Observable | null = null; + + readonly sportProgress$ = this.sportProgress.asReadonly(); + readonly gamingProgress$ = this.gamingProgress.asReadonly(); + readonly isLoading$ = this.isLoading.asReadonly(); + readonly loadingProgress$ = this.loadingProgress.asReadonly(); + readonly loadingText$ = this.loadingText.asReadonly(); + + readonly overallSportProgress = computed(() => { + const progress = this.sportProgress(); + if (!progress) { + return 0; + } + return progress.overallPercentage; + }); + + readonly overallGamingProgress = computed(() => { + const progress = this.gamingProgress(); + if (!progress) { + return 0; + } + const total = + progress.tft.progress + + progress.lol.progress + + progress.rocketLeague.progress + + progress.faceit.progress; + return total / 4; + }); + + /** + * Load all sport goals (cached after first load) + */ + loadSportGoals(forceRefresh = false): Observable { + // Return cached data if available and not forcing refresh + if (!forceRefresh && this.sportProgress() !== null) { + return of(this.sportProgress()!); + } + + // Return existing request if already in progress + if (this.sportGoalsRequest$ && !forceRefresh) { + return this.sportGoalsRequest$; + } + + const goals = environment.goals.sport; + + this.sportGoalsRequest$ = combineLatest({ + bike: this.stravaService.getSportProgress('bike', goals.bike), + run: this.stravaService.getSportProgress('run', goals.run), + swim: this.stravaService.getSportProgress('swim', goals.swim), + }).pipe( + map((progress) => { + // Calculate overall percentage as average of the three sports + const overallPercentage = Math.min( + 100, + (progress.bike.percentage + progress.run.percentage + progress.swim.percentage) / 3 + ); + + const sportProgress: SportProgress = { + bike: progress.bike, + run: progress.run, + swim: progress.swim, + overallPercentage, + }; + + this.sportProgress.set(sportProgress); + return sportProgress; + }), + catchError(() => { + const empty: SportProgress = { + bike: this.getEmptySportGoal('bike', goals.bike), + run: this.getEmptySportGoal('run', goals.run), + swim: this.getEmptySportGoal('swim', goals.swim), + overallPercentage: 0, + }; + this.sportProgress.set(empty); + return of(empty); + }), + shareReplay(1) + ); + + return this.sportGoalsRequest$; + } + + /** + * Load all gaming goals (cached after first load) + */ + loadGamingGoals(forceRefresh = false): Observable { + // Return cached data if available and not forcing refresh + if (!forceRefresh && this.gamingProgress() !== null) { + return of(this.gamingProgress()!); + } + + // Return existing request if already in progress + if (this.gamingGoalsRequest$ && !forceRefresh) { + return this.gamingGoalsRequest$; + } + + this.gamingGoalsRequest$ = combineLatest({ + tft: this.riotService.getTFTRank(), + lol: this.riotService.getLoLRank(), + rocketLeague: this.trackerService.getRocketLeagueRank(), + faceit: this.faceitService.getFaceitProgress(), + }).pipe( + map((progress) => { + const gamingProgress: GamingProgress = { + tft: progress.tft, + lol: progress.lol, + rocketLeague: progress.rocketLeague, + faceit: progress.faceit, + }; + + this.gamingProgress.set(gamingProgress); + return gamingProgress; + }), + catchError(() => { + const empty: GamingProgress = { + tft: this.getEmptyGamingGoal('tft', 'Diamond'), + lol: this.getEmptyGamingGoal('lol', 'Diamond'), + rocketLeague: this.getEmptyGamingGoal('rocket-league', 'Champion'), + faceit: this.getEmptyGamingGoal('faceit', 'Level 10'), + }; + this.gamingProgress.set(empty); + return of(empty); + }), + shareReplay(1) + ); + + return this.gamingGoalsRequest$; + } + + /** + * Preload all data with progress tracking + */ + preloadAllData(): Observable<{ sport: SportProgress; gaming: GamingProgress }> { + this.isLoading.set(true); + this.loadingProgress.set(0); + this.loadingText.set('Loading sport data...'); + + const totalSteps = 7; // 3 sports + 4 gaming services + let completedSteps = 0; + + const updateProgress = (stepName: string): void => { + completedSteps++; + const progress = Math.min(90, Math.round((completedSteps / totalSteps) * 85)); + this.loadingProgress.set(progress); + this.loadingText.set(stepName); + }; + + // Create observables with progress tracking + const goals = environment.goals.sport; + const bike$ = this.stravaService.getSportProgress('bike', goals.bike).pipe( + map((result) => { + updateProgress('Loading biking data...'); + return result; + }) + ); + const run$ = this.stravaService.getSportProgress('run', goals.run).pipe( + map((result) => { + updateProgress('Loading running data...'); + return result; + }) + ); + const swim$ = this.stravaService.getSportProgress('swim', goals.swim).pipe( + map((result) => { + updateProgress('Loading swimming data...'); + return result; + }) + ); + const tft$ = this.riotService.getTFTRank().pipe( + map((result) => { + updateProgress('Loading TFT data...'); + return result; + }) + ); + const lol$ = this.riotService.getLoLRank().pipe( + map((result) => { + updateProgress('Loading LoL data...'); + return result; + }) + ); + const rocketLeague$ = this.trackerService.getRocketLeagueRank().pipe( + map((result) => { + updateProgress('Loading Rocket League data...'); + return result; + }) + ); + const faceit$ = this.faceitService.getFaceitProgress().pipe( + map((result) => { + updateProgress('Loading Faceit data...'); + return result; + }) + ); + + return forkJoin({ + sport: combineLatest({ bike: bike$, run: run$, swim: swim$ }).pipe( + map((progress) => { + const overallPercentage = Math.min( + 100, + (progress.bike.percentage + progress.run.percentage + progress.swim.percentage) / 3 + ); + const sportProgress: SportProgress = { + bike: progress.bike, + run: progress.run, + swim: progress.swim, + overallPercentage, + }; + this.sportProgress.set(sportProgress); + return sportProgress; + }), + catchError(() => { + const empty: SportProgress = { + bike: this.getEmptySportGoal('bike', goals.bike), + run: this.getEmptySportGoal('run', goals.run), + swim: this.getEmptySportGoal('swim', goals.swim), + overallPercentage: 0, + }; + this.sportProgress.set(empty); + return of(empty); + }) + ), + gaming: combineLatest({ + tft: tft$, + lol: lol$, + rocketLeague: rocketLeague$, + faceit: faceit$, + }).pipe( + map((progress) => { + const gamingProgress: GamingProgress = { + tft: progress.tft, + lol: progress.lol, + rocketLeague: progress.rocketLeague, + faceit: progress.faceit, + }; + this.gamingProgress.set(gamingProgress); + return gamingProgress; + }), + catchError(() => { + const empty: GamingProgress = { + tft: this.getEmptyGamingGoal('tft', 'Diamond'), + lol: this.getEmptyGamingGoal('lol', 'Diamond'), + rocketLeague: this.getEmptyGamingGoal('rocket-league', 'Champion'), + faceit: this.getEmptyGamingGoal('faceit', 'Level 10'), + }; + this.gamingProgress.set(empty); + return of(empty); + }) + ), + }).pipe( + map((result) => { + this.loadingText.set('Finalizing...'); + this.loadingProgress.set(95); + this.isLoading.set(false); + return result; + }), + catchError((error) => { + this.isLoading.set(false); + this.loadingText.set('Error loading data'); + throw error; + }) + ); + } + + /** + * Check if on track for sport goals + */ + isOnTrackForSport(progress: SportProgress): boolean { + const ideal = getIdealProgress(); + return progress.overallPercentage >= ideal; + } + + /** + * Get projection data for charts + */ + getSportProjection(progress: SportProgress): { + actual: number; + ideal: number; + projected: number; + } { + const ideal = getIdealProgress(); + const daysElapsed = this.getDaysElapsed(); + const totalDays = 365; + + if (daysElapsed === 0) { + return { actual: 0, ideal: 0, projected: 0 }; + } + + const dailyAverage = progress.overallPercentage / daysElapsed; + const projected = dailyAverage * totalDays; + + return { + actual: progress.overallPercentage, + ideal, + projected: Math.min(100, projected), + }; + } + + private getDaysElapsed(): number { + const now = new Date(); + const start = new Date('2026-01-01'); + const diff = now.getTime() - start.getTime(); + return Math.floor(diff / (1000 * 60 * 60 * 24)); + } + + private getEmptySportGoal(type: SportType, target: number): SportGoal { + return { + type, + target, + current: 0, + percentage: 0, + activities: [], + }; + } + + private getEmptyGamingGoal( + game: 'tft' | 'lol' | 'rocket-league' | 'faceit', + target: string + ): GamingGoal { + return { + game, + target, + current: 'Unranked', + progress: 0, + recentMatches: [], + }; + } +} + diff --git a/src/app/core/services/riot.service.ts b/src/app/core/services/riot.service.ts new file mode 100644 index 0000000..06bc1dc --- /dev/null +++ b/src/app/core/services/riot.service.ts @@ -0,0 +1,946 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of, forkJoin, from } from 'rxjs'; +import { map, catchError, switchMap, tap, shareReplay, concatMap, delay, toArray } from 'rxjs/operators'; +import { environment } from '../../../environments/environment'; +import { + GamingGoal, + GamingStats, + Match, + RankInfo, + Streak, +} from '../models/gaming-goal.model'; +import { RiotMatch } from '../models/api-response.model'; +import { + RiotAccount, + RiotLeagueEntry, + RiotSummoner, + TFTMatch, +} from '../models/api-response.model'; + +@Injectable({ + providedIn: 'root', +}) +export class RiotService { + readonly #http = inject(HttpClient); + readonly #region = environment.riot.region; + readonly #baseUrl = `/api/proxy/riot/${this.#region}`; + readonly #accountBaseUrl = this.#getAccountRoutingUrl(); + + // Cache for account data per game type (5 minute TTL) + // Puuid is encrypted per app, so we need separate caches for LoL and TFT + #accountCache: Map<'lol' | 'tft', { data: RiotAccount; timestamp: number }> = new Map(); + // Cache for account Observable per game type to prevent duplicate requests + #accountObservable$: Map<'lol' | 'tft', Observable> = new Map(); + readonly #CACHE_TTL = 5 * 60 * 1000; // 5 minutes + + /** + * Maps region code to Account API routing value + */ + #getAccountRoutingUrl(): string { + const routingMap: Record = { + na1: 'americas', + la1: 'americas', + la2: 'americas', + br1: 'americas', + euw1: 'europe', + eun1: 'europe', + tr1: 'europe', + ru: 'europe', + kr: 'asia', + jp1: 'asia', + oc1: 'sea', + ph2: 'sea', + sg2: 'sea', + th2: 'sea', + tw2: 'sea', + vn2: 'sea', + }; + const routing = routingMap[this.#region] || 'europe'; + return `/api/proxy/riot/${routing}`; + } + + /** + * Checks if API key is configured for a game type + */ + #hasApiKey(_gameType: 'lol' | 'tft'): boolean { + return true; + } + + /** + * Get LoL puuid (for Load More functionality) + */ + getLoLPuuid(): Observable { + const gameName = environment.riot.summonerNames.lol; + const tagLine = environment.riot.tagLine; + if (!gameName || !tagLine || !this.#hasApiKey('lol')) { + return of(null); + } + return this.#getAccountByRiotId(gameName, tagLine, 'lol').pipe( + map((account) => account?.puuid || null), + catchError(() => of(null)) + ); + } + + /** + * Get TFT puuid (for Load More functionality) + */ + getTFTPuuid(): Observable { + const gameName = environment.riot.summonerNames.tft || environment.riot.summonerNames.lol; + const tagLine = environment.riot.tagLine; + if (!gameName || !tagLine || !this.#hasApiKey('tft')) { + return of(null); + } + return this.#getAccountByRiotId(gameName, tagLine, 'tft').pipe( + map((account) => account?.puuid || null), + catchError(() => of(null)) + ); + } + + /** + * Get LoL rank and progress + */ + getLoLRank(): Observable { + const gameName = environment.riot.summonerNames.lol; + const tagLine = environment.riot.tagLine; + if (!gameName || !tagLine || !this.#hasApiKey('lol')) { + return of(this.#getEmptyGoal('lol', 'Diamond')); + } + + // LoL API only allows by-puuid method (not by-name) + return this.#getAccountByRiotId(gameName, tagLine, 'lol').pipe( + switchMap((account) => { + if (!account?.puuid || account.puuid === 'undefined') { + console.error('Invalid account puuid received:', account); + throw new Error('Failed to get valid account puuid'); + } + console.log('[LoL] Account fetched, puuid:', account.puuid); + console.log('[LoL] Fetching summoner and league entry...'); + return forkJoin({ + rank: this.#getLeagueEntryByPuuid(account.puuid), + matches: this.#getLoLMatches(account.puuid), + }).pipe( + map((data) => { + console.log('[LoL] League entry data received:', data.rank); + const entry = data.rank[0]; + const rankInfo: RankInfo = entry + ? { + tier: entry.tier, + rank: entry.rank, + leaguePoints: entry.leaguePoints, + wins: entry.wins, + losses: entry.losses, + hotStreak: entry.hotStreak, + veteran: entry.veteran, + freshBlood: entry.freshBlood, + } + : { + tier: 'UNRANKED', + leaguePoints: 0, + wins: 0, + losses: 0, + }; + + return this.#mapToGamingGoal('lol', 'Diamond', { + rank: rankInfo, + matches: data.matches, + }); + }) + ); + }), + catchError((error) => { + console.error('Error fetching LoL rank:', error); + if (error.status === 400) { + console.warn( + 'LoL API returned 400. The player may not have played LoL in this region (EUNE), ' + + 'or the puuid is not valid for LoL endpoints. Showing unranked.' + ); + } else if (error.status === 401 || error.status === 403) { + console.warn( + `Riot API returned ${error.status}. Your LoL API key may be expired or invalid. ` + + 'Riot API keys expire after 24 hours. ' + + 'Get a new key at: https://developer.riotgames.com/' + ); + } else if (error.status === 429) { + console.warn( + 'Riot API rate limit exceeded. Please wait before making more requests.' + ); + } + return of(this.#getEmptyGoal('lol', 'Diamond')); + }), + shareReplay(1) + ); + } + + /** + * Get TFT rank and progress + */ + getTFTRank(): Observable { + const gameName = + environment.riot.summonerNames.tft || + environment.riot.summonerNames.lol; + const tagLine = environment.riot.tagLine; + if (!gameName || !tagLine || !this.#hasApiKey('tft')) { + return of(this.#getEmptyGoal('tft', 'Diamond')); + } + + return this.#getAccountByRiotId(gameName, tagLine, 'tft').pipe( + switchMap((account) => { + if (!account?.puuid || account.puuid === 'undefined') { + console.error('Invalid account puuid received:', account); + throw new Error('Failed to get valid account puuid'); + } + return forkJoin({ + rank: this.#getTFTLeagueEntryByPuuid(account.puuid), + matches: this.#getTFTMatches(account.puuid), + }).pipe( + map((data) => { + const entry = data.rank[0]; + const rankInfo: RankInfo = entry + ? { + tier: entry.tier, + rank: entry.rank, + leaguePoints: entry.leaguePoints, + wins: entry.wins, + losses: entry.losses, + hotStreak: entry.hotStreak, + veteran: entry.veteran, + freshBlood: entry.freshBlood, + } + : { + tier: 'UNRANKED', + leaguePoints: 0, + wins: 0, + losses: 0, + }; + + return this.#mapToGamingGoal('tft', 'Diamond', { + rank: rankInfo, + matches: data.matches, + }); + }) + ); + }), + catchError((error) => { + console.error('Error fetching TFT rank:', error); + return of(this.#getEmptyGoal('tft', 'Diamond')); + }), + shareReplay(1) + ); + } + + /** + * Get account by Riot ID (gameName and tagLine) + * This is the new way to get puuid + * @param gameType - Used to determine which API key to use + */ + #getAccountByRiotId( + gameName: string, + tagLine: string, + gameType: 'lol' | 'tft' + ): Observable { + // Check data cache first for this specific game type + const cached = this.#accountCache.get(gameType); + if (cached) { + const cacheAge = Date.now() - cached.timestamp; + if (cacheAge < this.#CACHE_TTL) { + // Cache is still valid + return of(cached.data); + } + // Cache expired but exists - use it as fallback if request fails + } + + // If there's already an in-flight request for this game type, return it + const existingObservable = this.#accountObservable$.get(gameType); + if (existingObservable) { + return existingObservable; + } + + const url = `${this.#accountBaseUrl}/riot/account/v1/accounts/by-riot-id/${encodeURIComponent(gameName)}/${encodeURIComponent(tagLine)}`; + // API key is added by the interceptor based on X-Riot-Game-Type header + // Puuid is encrypted per app, so we fetch separately for each game type + const accountObservable$ = this.#http.get(url, { + headers: { 'X-Riot-Game-Type': gameType }, + }).pipe( + tap((account) => { + // Cache successful response per game type + if (account?.puuid) { + this.#accountCache.set(gameType, { data: account, timestamp: Date.now() }); + } + // Clear the Observable cache after completion + this.#accountObservable$.delete(gameType); + }), + catchError((error) => { + console.error( + `Error fetching account by Riot ID (${gameName}#${tagLine}) for ${gameType.toUpperCase()}:`, + error + ); + if (error.status === 401 || error.status === 403) { + console.warn( + `Riot Account API returned ${error.status}. Your ${gameType.toUpperCase()} API key may be expired or invalid. ` + + 'Riot API keys expire after 24 hours. ' + + 'Get a new key at: https://developer.riotgames.com/' + ); + // If we have cached data for this game type (even expired), use it as fallback + const cached = this.#accountCache.get(gameType); + if (cached?.data?.puuid) { + console.warn( + `Using cached ${gameType.toUpperCase()} account data as fallback. Please refresh your API key to get updated data.` + ); + this.#accountObservable$.delete(gameType); + return of(cached.data); + } + } + // Clear the Observable cache on error + this.#accountObservable$.delete(gameType); + throw error; + }), + shareReplay(1) + ); + + // Store the Observable for this game type + this.#accountObservable$.set(gameType, accountObservable$); + return accountObservable$; + } + + /** + * Get summoner by name (region-specific, more reliable for LoL) + */ + #getSummonerByName(summonerName: string): Observable { + if (!summonerName) { + throw new Error('Invalid summoner name provided'); + } + const url = `${this.#baseUrl}/lol/summoner/v4/summoners/by-name/${encodeURIComponent(summonerName)}`; + // API key is added by the interceptor (detected from /lol/ path) + return this.#http.get(url).pipe( + catchError((error) => { + console.error( + `Error fetching summoner by name ${summonerName}:`, + error + ); + if (error.status === 404) { + console.warn( + 'LoL summoner not found. The player may not have played LoL in this region (EUNE).' + ); + } else if (error.status === 401 || error.status === 403) { + console.warn( + 'LoL API returned ' + error.status + '. Your LoL API key may be expired or invalid.' + ); + } + throw error; + }), + shareReplay(1) + ); + } + + /** + * Get summoner by puuid (used for TFT, but may fail for LoL if player hasn't played in region) + */ + #getSummonerByPuuid(puuid: string): Observable { + if (!puuid || puuid === 'undefined') { + console.error('getSummonerByPuuid called with invalid puuid:', puuid); + throw new Error('Invalid puuid provided'); + } + const url = `${this.#baseUrl}/lol/summoner/v4/summoners/by-puuid/${encodeURIComponent(puuid)}`; + console.log('[LoL] Fetching summoner by puuid:', puuid); + // API key is added by the interceptor (detected from /lol/ path) + return this.#http.get(url).pipe( + tap((summoner) => { + console.log('[LoL] Summoner fetched, id:', summoner?.id); + }), + catchError((error) => { + console.error( + `Error fetching summoner by puuid ${puuid}:`, + error + ); + if (error.status === 400) { + console.warn( + 'LoL summoner API returned 400. This might indicate: ' + + '1) The puuid is not valid for LoL in this region, ' + + '2) The player has not played LoL in this region, ' + + '3) The API endpoint format is incorrect.' + ); + } else if (error.status === 401 || error.status === 403) { + console.warn( + 'LoL API returned ' + error.status + '. Your LoL API key may be expired or invalid.' + ); + } + throw error; + }), + shareReplay(1) + ); + } + + /** + * Get LoL league entries by summoner ID + */ + #getLeagueEntryBySummonerId( + summonerId: string, + queueType: string + ): Observable { + if (!summonerId) { + console.error('getLeagueEntryBySummonerId called with undefined summonerId'); + return of([]); + } + const url = `${this.#baseUrl}/lol/league/v4/entries/by-summoner/${encodeURIComponent(summonerId)}`; + console.log('[LoL] Fetching league entry by summonerId:', summonerId, 'queueType:', queueType); + // API key is added by the interceptor (detected from /lol/ path) + return this.#http.get(url).pipe( + tap((entries) => { + console.log('[LoL] League entries received:', entries.length, 'entries'); + }), + map((entries) => entries.filter((e) => e.queueType === queueType)), + catchError((error) => { + console.error( + `Error fetching league entry by summonerId ${summonerId}:`, + error + ); + return of([]); + }) + ); + } + + /** + * Get LoL league entries by puuid directly + * Uses the direct endpoint: /lol/league/v4/entries/by-puuid/{encryptedPUUID} + * Prefers RANKED_SOLO_5x5, falls back to RANKED_FLEX_SR if SOLO doesn't exist + */ + #getLeagueEntryByPuuid(puuid: string): Observable { + if (!puuid || puuid === 'undefined') { + console.error('getLeagueEntryByPuuid called with invalid puuid:', puuid); + return of([]); + } + const url = `${this.#baseUrl}/lol/league/v4/entries/by-puuid/${encodeURIComponent(puuid)}`; + console.log('[LoL] Fetching league entry by puuid:', puuid); + // API key is added by the interceptor (detected from /lol/ path) + return this.#http.get(url).pipe( + tap((entries) => { + console.log('[LoL] League entries received:', entries.length, 'entries', entries); + }), + map((entries) => { + if (!entries || entries.length === 0) { + return []; + } + // Prefer RANKED_SOLO_5x5, fall back to RANKED_FLEX_SR + const soloEntry = entries.find((e) => e.queueType === 'RANKED_SOLO_5x5'); + if (soloEntry) { + return [soloEntry]; + } + const flexEntry = entries.find((e) => e.queueType === 'RANKED_FLEX_SR'); + if (flexEntry) { + return [flexEntry]; + } + // If neither exists, return the first entry (or empty array) + return entries.length > 0 ? [entries[0]] : []; + }), + catchError((error) => { + console.error( + `Error fetching league entry by puuid ${puuid}:`, + error + ); + if (error.status === 400) { + console.warn( + 'LoL league API returned 400. The player may not have played LoL in this region, ' + + 'or the puuid is not valid for LoL league endpoints.' + ); + } else if (error.status === 401 || error.status === 403) { + console.warn( + 'LoL API returned ' + error.status + '. Your LoL API key may be expired or invalid.' + ); + } + return of([]); + }) + ); + } + + /** + * Get TFT league entries by puuid + */ + #getTFTLeagueEntryByPuuid( + puuid: string + ): Observable { + if (!puuid) { + console.error('getTFTLeagueEntryByPuuid called with undefined puuid'); + return of([]); + } + // TFT endpoint: /tft/league/v1/by-puuid/{puuid} (no "entries" in path) + const url = `${this.#baseUrl}/tft/league/v1/by-puuid/${encodeURIComponent(puuid)}`; + // API key is added by the interceptor (detected from /tft/ path) + return this.#http.get(url).pipe( + catchError((error) => { + console.error( + `Error fetching TFT league entry by puuid ${puuid}:`, + error + ); + return of([]); + }) + ); + } + + /** + * Get recent LoL matches for a player + */ + #getLoLMatches(puuid: string): Observable { + if (!puuid || puuid === 'undefined') { + console.warn('getLoLMatches called with invalid puuid:', puuid); + return of([]); + } + // Match v5 API uses regional routing + const matchListUrl = `${this.#accountBaseUrl}/lol/match/v5/matches/by-puuid/${encodeURIComponent(puuid)}/ids`; + + return this.#http.get(matchListUrl, { + headers: { 'X-Riot-Game-Type': 'lol' }, + params: { start: '0', count: '5', type: 'ranked' }, + }).pipe( + switchMap((matchIds) => { + if (!matchIds?.length) { + return of([]); + } + // Fetch matches in batches of 5 to avoid rate limiting + const matchIdsToFetch = matchIds.slice(0, 5); + const batchSize = 5; + const batches: string[][] = []; + + for (let i = 0; i < matchIdsToFetch.length; i += batchSize) { + batches.push(matchIdsToFetch.slice(i, i + batchSize)); + } + + // Process batches sequentially with delay between batches + return from(batches).pipe( + concatMap((batch, index) => { + const batchRequests = batch.map((matchId) => + this.#http.get( + `${this.#accountBaseUrl}/lol/match/v5/matches/${matchId}`, + { headers: { 'X-Riot-Game-Type': 'lol' } } + ).pipe(catchError(() => of(null))) + ); + + return forkJoin(batchRequests).pipe( + // Add delay between batches (except first one) + delay(index === 0 ? 0 : 200) + ); + }), + // Collect all batches into a single array + toArray(), + map((allBatches) => { + // Flatten array of arrays + const flattened = allBatches.flat(); + return flattened + .filter((m): m is RiotMatch => m !== null) + .map((m) => this.#mapLoLMatch(m, puuid)); + }) + ); + }), + catchError((error) => { + if (error.status === 400) { + console.warn( + 'LoL match list API returned 400. This might indicate: ' + + '1) The puuid is not valid for LoL in this region, ' + + '2) The player has no ranked matches, ' + + '3) The API endpoint format is incorrect.' + ); + } else if (error.status === 401 || error.status === 403) { + console.warn( + 'LoL match list API returned ' + error.status + '. Your LoL API key may be expired or invalid.' + ); + } + return of([]); + }) + ); + } + + /** + * Get recent TFT matches for a player + */ + #getTFTMatches(puuid: string): Observable { + if (!puuid) { + return of([]); + } + // TFT Match v1 API + const matchListUrl = `${this.#accountBaseUrl}/tft/match/v1/matches/by-puuid/${encodeURIComponent(puuid)}/ids`; + + return this.#http.get(matchListUrl, { + headers: { 'X-Riot-Game-Type': 'tft' }, + params: { start: '0', count: '5' }, + }).pipe( + switchMap((matchIds) => { + if (!matchIds?.length) { + return of([]); + } + // Get details for matches + const matchRequests = matchIds.slice(0, 5).map((matchId) => + this.#http.get( + `${this.#accountBaseUrl}/tft/match/v1/matches/${matchId}`, + { headers: { 'X-Riot-Game-Type': 'tft' } } + ).pipe(catchError(() => of(null))) + ); + return forkJoin(matchRequests).pipe( + map((matches) => + matches + .filter((m): m is TFTMatch => m !== null) + .map((m) => this.#mapTFTMatch(m, puuid)) + ) + ); + }), + catchError(() => of([])) + ); + } + + /** + * Get more LoL matches (for Load More functionality) + */ + getMoreLoLMatches(puuid: string, startIndex: number, count: number = 5): Observable { + if (!puuid || puuid === 'undefined') { + console.warn('getMoreLoLMatches called with invalid puuid:', puuid); + return of([]); + } + const matchListUrl = `${this.#accountBaseUrl}/lol/match/v5/matches/by-puuid/${encodeURIComponent(puuid)}/ids`; + + return this.#http.get(matchListUrl, { + headers: { 'X-Riot-Game-Type': 'lol' }, + params: { start: startIndex.toString(), count: count.toString(), type: 'ranked' }, + }).pipe( + switchMap((matchIds) => { + if (!matchIds?.length) { + return of([]); + } + // Fetch matches in batches of 5 to avoid rate limiting + const matchIdsToFetch = matchIds.slice(0, count); + const batchSize = 5; + const batches: string[][] = []; + + for (let i = 0; i < matchIdsToFetch.length; i += batchSize) { + batches.push(matchIdsToFetch.slice(i, i + batchSize)); + } + + // Process batches sequentially with delay between batches + return from(batches).pipe( + concatMap((batch, index) => { + const batchRequests = batch.map((matchId) => + this.#http.get( + `${this.#accountBaseUrl}/lol/match/v5/matches/${matchId}`, + { headers: { 'X-Riot-Game-Type': 'lol' } } + ).pipe(catchError(() => of(null))) + ); + + return forkJoin(batchRequests).pipe( + // Add delay between batches (except first one) + delay(index === 0 ? 0 : 200) + ); + }), + // Collect all batches into a single array + toArray(), + map((allBatches) => { + // Flatten array of arrays + const flattened = allBatches.flat(); + return flattened + .filter((m): m is RiotMatch => m !== null) + .map((m) => this.#mapLoLMatch(m, puuid)); + }) + ); + }), + catchError((error) => { + console.error('Error fetching more LoL matches:', error); + return of([]); + }) + ); + } + + /** + * Get more TFT matches (for Load More functionality) + */ + getMoreTFTMatches(puuid: string, startIndex: number, count: number = 5): Observable { + if (!puuid || puuid === 'undefined') { + console.warn('getMoreTFTMatches called with invalid puuid:', puuid); + return of([]); + } + const matchListUrl = `${this.#accountBaseUrl}/tft/match/v1/matches/by-puuid/${encodeURIComponent(puuid)}/ids`; + + return this.#http.get(matchListUrl, { + headers: { 'X-Riot-Game-Type': 'tft' }, + params: { start: startIndex.toString(), count: count.toString() }, + }).pipe( + switchMap((matchIds) => { + if (!matchIds?.length) { + return of([]); + } + // Get details for matches + const matchRequests = matchIds.slice(0, count).map((matchId) => + this.#http.get( + `${this.#accountBaseUrl}/tft/match/v1/matches/${matchId}`, + { headers: { 'X-Riot-Game-Type': 'tft' } } + ).pipe(catchError(() => of(null))) + ); + return forkJoin(matchRequests).pipe( + map((matches) => + matches + .filter((m): m is TFTMatch => m !== null) + .map((m) => this.#mapTFTMatch(m, puuid)) + ) + ); + }), + catchError((error) => { + console.error('Error fetching more TFT matches:', error); + return of([]); + }) + ); + } + + /** + * Map Riot LoL match to Match model + */ + #mapLoLMatch(match: RiotMatch, puuid: string): Match { + const participant = match.info.participants.find((p) => p.puuid === puuid); + const isWin = participant?.win ?? false; + const gameName = environment.riot.summonerNames.lol; + const tagLine = environment.riot.tagLine; + + // Build op.gg match URL + // Format: https://www.op.gg/summoners/{region}/{name}-{tag}/matches/{matchId} + const opggRegion = this.#getOpggRegion(); + const matchUrl = `https://www.op.gg/summoners/${opggRegion}/${encodeURIComponent(gameName)}-${encodeURIComponent(tagLine)}/matches/${match.metadata.matchId}`; + + // Note: Riot API doesn't provide LP changes, so we don't include lpChange + + return { + id: match.metadata.matchId, + game: 'lol', + date: new Date(match.info.gameEndTimestamp), + result: isWin ? 'win' : 'loss', + champion: participant?.championName, + kda: participant + ? `${participant.kills}/${participant.deaths}/${participant.assists}` + : undefined, + duration: match.info.gameDuration, + matchUrl, + }; + } + + /** + * Map TFT match to Match model + */ + #mapTFTMatch(match: TFTMatch, puuid: string): Match { + const participant = match.info.participants.find((p) => p.puuid === puuid); + const placement = participant?.placement ?? 8; + // Top 4 is considered a win in TFT + const isWin = placement <= 4; + const gameName = environment.riot.summonerNames.tft || + environment.riot.summonerNames.lol; + const tagLine = environment.riot.tagLine; + + // Build tactics.tools match URL + // Format: https://tactics.tools/player/{region}/{name}/{tag} + const tacticsRegion = this.#getTacticsRegion(); + const matchUrl = `https://tactics.tools/player/${tacticsRegion}/${encodeURIComponent(gameName)}/${encodeURIComponent(tagLine)}`; + + // Note: Riot API doesn't provide LP changes, so we don't include lpChange + + return { + id: match.metadata.match_id, + game: 'tft', + date: new Date(match.info.game_datetime), + result: isWin ? 'win' : 'loss', + placement, + duration: match.info.game_length, + matchUrl, + }; + } + + /** + * Get op.gg region code from Riot region + */ + #getOpggRegion(): string { + const regionMap: Record = { + na1: 'na', + euw1: 'euw', + eun1: 'eune', + kr: 'kr', + br1: 'br', + la1: 'lan', + la2: 'las', + oc1: 'oce', + ru: 'ru', + tr1: 'tr', + jp1: 'jp', + }; + return regionMap[this.#region] || this.#region; + } + + /** + * Get tactics.tools region code from Riot region + */ + #getTacticsRegion(): string { + const regionMap: Record = { + na1: 'na', + euw1: 'euw', + eun1: 'eune', + kr: 'kr', + br1: 'br', + la1: 'lan', + la2: 'las', + oc1: 'oce', + ru: 'ru', + tr1: 'tr', + jp1: 'jp', + }; + return regionMap[this.#region] || this.#region; + } + + #mapToGamingGoal( + game: 'lol' | 'tft', + target: string, + data: { rank: RankInfo; matches: Match[] } + ): GamingGoal { + const current = this.#formatRank(data.rank); + const progress = this.#calculateRankProgress(data.rank, target); + const stats = this.#calculateStats(data.rank, data.matches, game); + + return { + game, + target, + current, + progress, + recentMatches: data.matches, + rankInfo: data.rank, + stats, + }; + } + + /** + * Calculate gaming stats from rank info and matches + */ + #calculateStats( + rank: RankInfo, + matches: Match[], + game: 'lol' | 'tft' + ): GamingStats { + const totalGames = rank.wins + rank.losses; + const winRate = totalGames > 0 ? (rank.wins / totalGames) * 100 : 0; + + // Calculate streak from recent matches + const streak = this.#calculateStreak(matches); + + // Count recent wins/losses + const recentWins = matches.filter((m) => m.result === 'win').length; + const recentLosses = matches.filter((m) => m.result === 'loss').length; + + // Calculate average placement for TFT + const avgPlacement = game === 'tft' && matches.length > 0 + ? matches.reduce((sum, m) => sum + (m.placement ?? 0), 0) / matches.length + : undefined; + + return { + winRate, + totalGames, + recentWins, + recentLosses, + streak, + avgPlacement, + }; + } + + /** + * Calculate current streak from recent matches + */ + #calculateStreak(matches: Match[]): Streak { + if (matches.length === 0) { + return { type: 'none', count: 0 }; + } + + const firstResult = matches[0].result; + if (firstResult === 'draw') { + return { type: 'none', count: 0 }; + } + + let count = 0; + for (const match of matches) { + if (match.result === firstResult) { + count++; + } else { + break; + } + } + + return { type: firstResult, count }; + } + + #formatRank(rank: RankInfo): string { + if (rank.tier === 'UNRANKED') { + return 'Unranked'; + } + return `${rank.tier} ${rank.rank || ''}`.trim(); + } + + #calculateRankProgress(rank: RankInfo, target: string): number { + const tierOrder = [ + 'IRON', + 'BRONZE', + 'SILVER', + 'GOLD', + 'PLATINUM', + 'EMERALD', + 'DIAMOND', + 'MASTER', + 'GRANDMASTER', + 'CHALLENGER', + ]; + + const targetIndex = tierOrder.indexOf(target.toUpperCase()); + const currentIndex = tierOrder.indexOf(rank.tier); + + if (currentIndex >= targetIndex) { + return 100; + } + + if (currentIndex < 0) { + return 0; + } + + // Calculate overall progress from Iron to target tier + // Each tier has 4 divisions (IV, III, II, I), each division requires ~100 LP + const divisionOrder = ['IV', 'III', 'II', 'I']; + const currentDivisionIndex = divisionOrder.indexOf(rank.rank || 'IV'); + + // Calculate total divisions from Iron (index 0) to target tier + // Example: Diamond (index 6) = 6 tiers * 4 divisions = 24 divisions total + const totalDivisionsToTarget = targetIndex * 4; + + // If already at or above target tier, return 100% + if (currentIndex >= targetIndex) { + return 100; + } + + // Calculate how many FULL divisions we've completed from Iron to current position + // Completed tiers: currentIndex tiers (each with 4 divisions) + // Completed divisions in current tier: currentDivisionIndex + const completedTiersDivisions = currentIndex * 4; + const completedDivisionsInCurrentTier = currentDivisionIndex; + + // Calculate LP progress within current division (0-100 LP per division) + const lpInDivision = Math.min(rank.leaguePoints, 100); + const lpProgressFraction = lpInDivision / 100; // 0.0 to 1.0 + + // Total completed divisions = completed tiers + completed divisions in current tier + LP progress + // Example: Emerald IV (index 5) with 46 LP targeting Diamond (index 6) + // - Completed tiers: 5 tiers * 4 = 20 divisions + // - Completed divisions in Emerald: 0 (we're in IV) + // - LP progress: 46/100 = 0.46 divisions + // - Total completed: 20 + 0 + 0.46 = 20.46 divisions + // - Total to target: 6 tiers * 4 = 24 divisions + // - Progress: 20.46 / 24 = 85.25% + const totalCompletedDivisions = completedTiersDivisions + completedDivisionsInCurrentTier + lpProgressFraction; + const totalProgress = (totalCompletedDivisions / totalDivisionsToTarget) * 100; + + return Math.min(100, Math.max(0, totalProgress)); + } + + #getEmptyGoal(game: 'lol' | 'tft', target: string): GamingGoal { + return { + game, + target, + current: 'Unranked', + progress: 0, + recentMatches: [], + }; + } +} diff --git a/src/app/core/services/strava.service.ts b/src/app/core/services/strava.service.ts new file mode 100644 index 0000000..a7d55e1 --- /dev/null +++ b/src/app/core/services/strava.service.ts @@ -0,0 +1,398 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable, of } from 'rxjs'; +import { map, catchError, tap, switchMap } from 'rxjs/operators'; +import { environment } from '../../../environments/environment'; +import { + Activity, + SportType, + SportGoal, +} from '../models/sport-goal.model'; +import { + StravaActivity, + StravaTokenResponse, +} from '../models/api-response.model'; +import { metersToKm, isIn2026 } from '../../shared/utils/date.utils'; + +const STRAVA_API_BASE = 'https://www.strava.com/api/v3'; +const STRAVA_AUTH_BASE = '/api/auth/strava'; +const STORAGE_KEY_TOKEN = 'strava_access_token'; +const STORAGE_KEY_REFRESH = 'strava_refresh_token'; +const STORAGE_KEY_EXPIRES = 'strava_token_expires'; + +@Injectable({ + providedIn: 'root', +}) +export class StravaService { + private readonly http = inject(HttpClient); + + /** + * Initiate OAuth flow by redirecting to Strava + */ + initiateOAuth(): void { + const clientId = environment.strava.clientId; + const redirectUri = environment.strava.redirectUri; + const scope = 'activity:read_all'; + const state = this.generateState(); + + const url = `${STRAVA_API_BASE}/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${scope}&state=${state}`; + + if (typeof window !== 'undefined') { + window.location.href = url; + } + } + + /** + * Exchange authorization code for access token (via backend proxy) + */ + exchangeCodeForToken(code: string): Observable { + return this.http + .post(`${STRAVA_AUTH_BASE}/token`, { code }) + .pipe( + tap((response) => { + this.storeToken(response); + }) + ); + } + + /** + * Refresh access token (via backend proxy) + */ + refreshToken(): Observable { + const refreshToken = this.getRefreshToken(); + if (!refreshToken) { + return new Observable((observer) => { + observer.error(new Error('No refresh token available. Please re-authenticate.')); + }); + } + + return this.http + .post(`${STRAVA_AUTH_BASE}/refresh`, { refresh_token: refreshToken }) + .pipe( + tap((response) => { + // Always store refreshed token in localStorage (takes precedence over expired env token) + this.storeToken(response); + console.log('Strava token refreshed successfully'); + }), + catchError((error) => { + // If refresh token is also invalid, clear stored tokens + if (error.status === 400 || error.status === 401) { + console.error('Refresh token is invalid. Please re-authenticate.'); + this.logout(); // Clear invalid tokens + } + throw error; + }) + ); + } + + /** + * Check if token is expired or about to expire (within 5 minutes) + */ + private isTokenExpired(): boolean { + const expires = this.getTokenExpires(); + if (!expires) { + // If no expiration set, assume valid (but might be expired on server) + return false; + } + // Consider expired if less than 5 minutes remaining + return Date.now() >= expires - 5 * 60 * 1000; + } + + /** + * Ensure token is valid, refresh if needed + */ + private ensureValidToken(): Observable { + const token = this.getAccessToken(); + if (!token) { + return new Observable((observer) => { + observer.error(new Error('No access token available')); + }); + } + + // If token is expired or about to expire, refresh it + if (this.isTokenExpired()) { + const refreshToken = this.getRefreshToken(); + if (refreshToken) { + return this.refreshToken().pipe( + map((response) => response.access_token), + catchError((error) => { + console.error('Failed to refresh token:', error); + // If refresh token is invalid, clear tokens and throw error + if (error.status === 400 || error.status === 401) { + this.logout(); + throw new Error('Refresh token is invalid. Please re-authenticate.'); + } + // For other errors, return original token as fallback + return of(token); + }) + ); + } + } + + return of(token); + } + + /** + * Get activities for 2026 + */ + getActivities(): Observable { + return this.ensureValidToken().pipe( + switchMap((token) => { + const headers = { Authorization: `Bearer ${token}` }; + const params = new HttpParams() + .set('per_page', '200') + .set('page', '1'); + + return this.http + .get(`${STRAVA_API_BASE}/athlete/activities`, { + headers, + params, + }) + .pipe( + map((activities) => + activities + .filter((activity) => { + const date = new Date(activity.start_date); + return isIn2026(date); + }) + .map((activity) => this.mapToActivity(activity)) + .filter((activity): activity is Activity => activity !== null) + ), + catchError((error) => { + // Try to refresh token on 401 (in case automatic refresh didn't work) + if (error.status === 401) { + const refreshToken = this.getRefreshToken(); + if (refreshToken) { + return this.refreshToken().pipe( + switchMap((response) => { + const newHeaders = { + Authorization: `Bearer ${response.access_token}`, + }; + return this.http.get( + `${STRAVA_API_BASE}/athlete/activities`, + { + headers: newHeaders, + params, + } + ); + }), + map((activities) => + activities + .filter((activity) => { + const date = new Date(activity.start_date); + return isIn2026(date); + }) + .map((activity) => this.mapToActivity(activity)) + .filter((activity): activity is Activity => activity !== null) + ), + catchError((refreshError) => { + console.error( + 'Error fetching Strava activities after refresh:', + refreshError + ); + if (refreshError.status === 400 || refreshError.status === 401) { + console.error( + 'Refresh token is also invalid. Please re-authenticate via OAuth.' + ); + } + return of([]); + }) + ); + } else { + console.error( + 'No refresh token available. Please re-authenticate via OAuth.' + ); + } + } + console.error('Error fetching Strava activities:', error); + return of([]); + }) + ); + }), + catchError(() => { + console.error('No valid token available for Strava'); + return of([]); + }) + ); + } + + /** + * Get sport progress for a specific type + */ + getSportProgress(type: SportType, target: number): Observable { + return this.getActivities().pipe( + map((activities) => { + const filtered = activities.filter((a) => a.type === type); + const totalDistance = filtered.reduce( + (sum, activity) => sum + metersToKm(activity.distance), + 0 + ); + const percentage = Math.min(100, (totalDistance / target) * 100); + + return { + type, + target, + current: totalDistance, + percentage, + activities: filtered, + }; + }) + ); + } + + /** + * Check if user is authenticated + */ + isAuthenticated(): boolean { + const token = this.getAccessToken(); + if (!token) { + return false; + } + const expires = this.getTokenExpires(); + // If no expiration set (environment token), assume valid + if (!expires) { + return true; + } + return Date.now() < expires; + } + + /** + * Logout - clear stored tokens + */ + logout(): void { + if (typeof localStorage !== 'undefined') { + localStorage.removeItem(STORAGE_KEY_TOKEN); + localStorage.removeItem(STORAGE_KEY_REFRESH); + localStorage.removeItem(STORAGE_KEY_EXPIRES); + } + } + + private mapToActivity(activity: StravaActivity): Activity | null { + const sportType = this.mapSportType(activity.sport_type || activity.type); + // If we can't categorize the activity, return null to exclude it + if (!sportType) { + return null; + } + return { + id: activity.id, + name: activity.name, + type: sportType, + distance: activity.distance, + startDate: new Date(activity.start_date), + movingTime: activity.moving_time, + }; + } + + /** + * Map Strava activity type to our SportType + * Returns null if activity type doesn't match bike, run, or swim + */ + private mapSportType(stravaType: string | undefined): SportType | null { + if (!stravaType) { + return null; + } + + const normalized = stravaType.toLowerCase(); + + // Bike activities + if ( + normalized === 'ride' || + normalized === 'ebikeride' || + normalized === 'handcycle' || + normalized === 'virtualride' || + normalized.includes('bike') || + normalized.includes('cycle') + ) { + return 'bike'; + } + + // Run activities + if ( + normalized === 'run' || + normalized === 'trailrun' || + normalized === 'walk' || + normalized.includes('run') + ) { + return 'run'; + } + + // Swim activities + if ( + normalized === 'swim' || + normalized === 'openwaterswim' || + normalized.includes('swim') + ) { + return 'swim'; + } + + // Unknown activity type - don't default to bike, return null to exclude + return null; + } + + private storeToken(response: StravaTokenResponse): void { + if (typeof localStorage !== 'undefined') { + localStorage.setItem(STORAGE_KEY_TOKEN, response.access_token); + localStorage.setItem(STORAGE_KEY_REFRESH, response.refresh_token); + localStorage.setItem( + STORAGE_KEY_EXPIRES, + String(response.expires_at * 1000) + ); + } + } + + private getAccessToken(): string | null { + // Check localStorage first (refreshed tokens take precedence) + if (typeof localStorage !== 'undefined') { + const storedToken = localStorage.getItem(STORAGE_KEY_TOKEN); + if (storedToken) { + // Check if stored token is still valid + const storedExpires = localStorage.getItem(STORAGE_KEY_EXPIRES); + if (storedExpires) { + const expires = Number.parseInt(storedExpires, 10); + // If stored token is still valid (or expired but we'll refresh), use it + if (Date.now() < expires + 5 * 60 * 1000) { + return storedToken; + } + } else { + // No expiration info, use it + return storedToken; + } + } + } + // Fall back to environment token (for initial setup) + if (environment.strava.accessToken) { + return environment.strava.accessToken; + } + return null; + } + + private getRefreshToken(): string | null { + // First check environment (for single-page app without login) + if (environment.strava.refreshToken) { + return environment.strava.refreshToken; + } + // Fall back to localStorage (from OAuth flow) + if (typeof localStorage === 'undefined') { + return null; + } + return localStorage.getItem(STORAGE_KEY_REFRESH); + } + + private getTokenExpires(): number | null { + // First check environment (for single-page app without login) + if (environment.strava.tokenExpiresAt) { + return environment.strava.tokenExpiresAt; + } + // Fall back to localStorage (from OAuth flow) + if (typeof localStorage === 'undefined') { + return null; + } + const expires = localStorage.getItem(STORAGE_KEY_EXPIRES); + return expires ? Number.parseInt(expires, 10) : null; + } + + private generateState(): string { + return Math.random().toString(36).substring(2, 15); + } +} + diff --git a/src/app/core/services/tracker-gg.service.ts b/src/app/core/services/tracker-gg.service.ts new file mode 100644 index 0000000..9126b53 --- /dev/null +++ b/src/app/core/services/tracker-gg.service.ts @@ -0,0 +1,145 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; +import { environment } from '../../../environments/environment'; +import { GamingGoal, Match } from '../models/gaming-goal.model'; +import { TrackerGGProfile } from '../models/api-response.model'; + +const TRACKER_API_BASE = + '/api/proxy/tracker/api/v1/rocket-league/standard/profile'; + +@Injectable({ + providedIn: 'root', +}) +export class TrackerGGService { + private readonly http = inject(HttpClient); + private readonly platform = environment.tracker.rocketLeague.platform; + private readonly username = environment.tracker.rocketLeague.username; + + /** + * Get Rocket League rank and progress (via backend proxy) + */ + getRocketLeagueRank(): Observable { + if (!this.platform || !this.username) { + return of(this.getEmptyGoal()); + } + + const url = `${TRACKER_API_BASE}/${this.platform}/${encodeURIComponent(this.username)}`; + + return this.http + .get(url) + .pipe( + map((profile) => { + // Check if response has expected structure + if (!profile || !profile.data || !profile.data.segments) { + console.warn('Unexpected Tracker.gg API response structure:', profile); + return this.getEmptyGoal(); + } + + const rankSegment = profile.data.segments.find( + (s) => s.type === 'playlist' + ); + + if (!rankSegment) { + console.warn('No playlist segment found in Tracker.gg response'); + return this.getEmptyGoal(); + } + + const rankName = + rankSegment.attributes?.rank?.metadata?.rankName || 'Unranked'; + const tierName = + rankSegment.attributes?.rank?.metadata?.tierName || ''; + const current = tierName + ? `${tierName} ${rankName}`.trim() + : rankName; + + const progress = this.calculateRankProgress( + tierName, + 'Champion' + ); + + const goal: GamingGoal = { + game: 'rocket-league', + target: 'Champion', + current, + progress, + recentMatches: [], + }; + return goal; + }), + catchError((error) => { + console.error('Error fetching Rocket League rank:', error); + if (error.error) { + // Try to log the error response + if (typeof error.error === 'string') { + console.error('Error response (string):', error.error.substring(0, 500)); + } else { + console.error('Error response (object):', error.error); + } + } + if (error.status === 0) { + console.warn( + 'CORS error detected. Tracker.gg API only works server-side (SSR). ' + + 'Run the app with SSR to fetch Rocket League data.' + ); + } else if (error.status === 200 && error.error) { + console.warn( + 'Response parsing failed. The API might have returned HTML or invalid JSON. ' + + 'This could indicate an invalid API key or incorrect endpoint.' + ); + console.warn('Full error:', error); + } else if (error.status === 401 || error.status === 403) { + console.warn( + 'Tracker.gg API authentication failed. Verify your API key is valid.' + ); + } else if (error.status === 404) { + console.warn( + 'Rocket League profile not found. Verify your platform and username are correct.' + ); + } + return of(this.getEmptyGoal()); + }) + ); + } + + private calculateRankProgress(current: string, target: string): number { + const rankOrder = [ + 'Unranked', + 'Bronze', + 'Silver', + 'Gold', + 'Platinum', + 'Diamond', + 'Champion', + 'Grand Champion', + 'Supersonic Legend', + ]; + + const targetIndex = rankOrder.indexOf(target); + const currentIndex = rankOrder.findIndex((r) => + current.toLowerCase().includes(r.toLowerCase()) + ); + + if (currentIndex >= targetIndex) { + return 100; + } + + if (currentIndex < 0) { + return 0; + } + + return (currentIndex / targetIndex) * 100; + } + + private getEmptyGoal(): GamingGoal { + return { + game: 'rocket-league', + target: 'Champion', + current: 'Unranked', + progress: 0, + recentMatches: [], + }; + } +} + diff --git a/src/app/features/dashboard/dashboard.component.scss b/src/app/features/dashboard/dashboard.component.scss new file mode 100644 index 0000000..c649b78 --- /dev/null +++ b/src/app/features/dashboard/dashboard.component.scss @@ -0,0 +1,17 @@ +// Component styles if needed + + + + + + + + + + + + + + + + diff --git a/src/app/features/dashboard/dashboard.component.ts b/src/app/features/dashboard/dashboard.component.ts new file mode 100644 index 0000000..8096078 --- /dev/null +++ b/src/app/features/dashboard/dashboard.component.ts @@ -0,0 +1,560 @@ +import { Component, OnInit, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterLink } from '@angular/router'; +import { GoalsService } from '../../core/services/goals.service'; +import { ThreeTrophyComponent } from '../../shared/components/three-trophy/three-trophy.component'; +import { ThreeGeometricComponent } from '../../shared/components/three-geometric/three-geometric.component'; +import { ProgressRingComponent } from '../../shared/components/progress-ring/progress-ring.component'; +import { AnimatedCounterComponent } from '../../shared/components/animated-counter/animated-counter.component'; +import { ConfettiComponent } from '../../shared/components/confetti/confetti.component'; + +@Component({ + selector: 'app-dashboard', + standalone: true, + imports: [ + CommonModule, + RouterLink, + ThreeTrophyComponent, + ThreeGeometricComponent, + ProgressRingComponent, + AnimatedCounterComponent, + ConfettiComponent, + ], + template: ` +
+ + + +
+ +
+
+
+

+ 2026 Goals +

+

+ Track your progress, achieve your dreams +

+
+
+
🎯
+
+ {{ overallProgress.toFixed(1) }}% +
+

Overall Progress

+
+
+
+
+ + + +
+
+ + +
+ +
+
+

Overall Achievement

+

Your journey to success visualized

+
+
+ +
+
+
+
+
+ + +
+ +
+
+
+
+

Sport Goals

+

+ Track your fitness journey across biking, running, and swimming +

+
+
+ {{ sportProgress.toFixed(1) }}% +
+
+
+
+
+
+
+ + + View Details → + +
+
+ +
+

Sport Goals Breakdown

+
+
+
🚴
+

Biking

+

Target: 7,500 km

+
+
+
+
+
+
🏃
+

Running

+

Target: 2,500 km

+
+
+
+
+
+
🏊
+

Swimming

+

Target: 250 km

+
+
+
+
+
+
+
+
+
+ + +
+ +
+
+
+
+

Gaming Goals

+

+ Climb the ranks across multiple competitive games +

+
+
+ {{ gamingProgress.toFixed(1) }}% +
+
+
+
+
+
+
+ + + View Details → + +
+
+ +
+

Gaming Goals Breakdown

+
+
+
🎮
+

TFT

+

Target: Diamond

+
+
+
+
+
+
⚔️
+

LoL

+

Target: Diamond

+
+
+
+
+
+
🚗
+

Rocket League

+

Target: Champion

+
+
+
+
+
+
🎯
+

Faceit

+

Target: Level 10

+
+
+
+
+
+
+
+
+
+ + +
+ +
+
+

Quick Overview

+
+
+
🏃
+
+ +
+

Sport Goals

+
+ +
+

Fitness progress

+
+ +
+
🎮
+
+ +
+

Gaming Goals

+
+ +
+

Rank progress

+
+ +
+
📊
+
+ +
+

Overall

+
+ +
+

Total progress

+
+ +
+
+
+
+ +
+
+

Days Remaining

+

Until 2026 ends

+
+
+
+
+
+
+ `, + styles: [ + ` + .dashboard-scroll-container { + height: 100vh; + overflow-y: scroll; + scroll-snap-type: y mandatory; + scroll-behavior: smooth; + } + + .full-page-section { + height: 100vh; + width: 100%; + position: relative; + scroll-snap-align: start; + scroll-snap-stop: always; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + } + + .hero-section { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + } + + .trophy-section { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); + } + + .sport-section { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); + } + + .gaming-section { + background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); + } + + .stats-section { + background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); + } + + .section-title-large { + font-size: 4rem; + font-weight: 900; + color: white; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); + } + + @media (min-width: 768px) { + .section-title-large { + font-size: 5rem; + } + } + + .section-subtitle-large { + font-size: 1.5rem; + color: rgba(255, 255, 255, 0.9); + font-weight: 500; + } + + @media (min-width: 768px) { + .section-subtitle-large { + font-size: 2rem; + } + } + + .stat-card { + background: white; + backdrop-filter: blur(20px); + border-radius: 2rem; + padding: 3rem 2rem; + text-align: center; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + border: 2px solid rgba(255, 255, 255, 0.5); + transition: all 0.3s ease; + } + + .stat-card:hover { + transform: translateY(-10px) scale(1.05); + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + @keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .animate-fade-in-up { + animation: fade-in-up 1s ease-out; + } + + .animate-fade-in-up-delay { + animation: fade-in-up 1s ease-out 0.3s both; + } + + @keyframes expand { + from { + opacity: 0; + max-height: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + max-height: 1000px; + transform: translateY(0); + } + } + + .animate-expand { + animation: expand 0.5s ease-out; + overflow: hidden; + } + + @keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } + } + + .animate-shimmer { + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.4) 50%, + transparent 100% + ); + background-size: 200% 100%; + animation: shimmer 3s infinite; + } + + /* Hide scrollbar but keep functionality */ + .dashboard-scroll-container::-webkit-scrollbar { + width: 8px; + } + + .dashboard-scroll-container::-webkit-scrollbar-track { + background: transparent; + } + + .dashboard-scroll-container::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + border-radius: 4px; + } + + .dashboard-scroll-container::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.5); + } + `, + ], +}) +export class DashboardComponent implements OnInit { + loading = true; + sportProgress = 0; + gamingProgress = 0; + overallProgress = 0; + daysRemaining = 0; + showConfetti = false; + + sportExpanded = signal(false); + gamingExpanded = signal(false); + + readonly Math = Math; + + constructor(private readonly goalsService: GoalsService) {} + + ngOnInit(): void { + this.loadData(); + this.calculateDaysRemaining(); + } + + toggleSportExpanded(): void { + this.sportExpanded.set(!this.sportExpanded()); + } + + toggleGamingExpanded(): void { + this.gamingExpanded.set(!this.gamingExpanded()); + } + + loadData(): void { + this.loading = true; + + this.goalsService.loadSportGoals().subscribe({ + next: (progress) => { + this.sportProgress = progress.overallPercentage; + this.updateOverallProgress(); + this.loading = false; + }, + error: () => { + this.loading = false; + }, + }); + + this.goalsService.loadGamingGoals().subscribe({ + next: (progress) => { + this.gamingProgress = + (progress.tft.progress + + progress.lol.progress + + progress.rocketLeague.progress + + progress.faceit.progress) / + 4; + this.updateOverallProgress(); + }, + error: () => { + // Error already handled + }, + }); + } + + private updateOverallProgress(): void { + const newProgress = (this.sportProgress + this.gamingProgress) / 2; + const wasComplete = this.overallProgress >= 100; + this.overallProgress = newProgress; + + if (!wasComplete && this.overallProgress >= 100) { + this.showConfetti = true; + setTimeout(() => { + this.showConfetti = false; + }, 5000); + } + } + + private calculateDaysRemaining(): void { + const now = new Date(); + const end = new Date('2026-12-31'); + const diff = end.getTime() - now.getTime(); + this.daysRemaining = Math.max(0, Math.ceil(diff / (1000 * 60 * 60 * 24))); + } +} diff --git a/src/app/features/gaming/gaming-goals.component.scss b/src/app/features/gaming/gaming-goals.component.scss new file mode 100644 index 0000000..dc2c530 --- /dev/null +++ b/src/app/features/gaming/gaming-goals.component.scss @@ -0,0 +1,641 @@ +.gaming-container { + max-width: 1200px; + margin: 0 auto; + padding: 1.5rem; +} + +.header { + margin-bottom: 2rem; +} + +.title { + font-size: 2rem; + font-weight: 700; + color: #1f2937; + margin: 0 0 0.5rem 0; +} + +.subtitle { + color: #6b7280; + margin: 0; +} + +/* Loading State */ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 300px; +} + +.loading-spinner { + width: 48px; + height: 48px; + border: 4px solid #e5e7eb; + border-top-color: #8b5cf6; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loading-text { + margin-top: 1rem; + color: #6b7280; +} + +/* Overall Card */ +.overall-card { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 1rem; + padding: 1.5rem; + margin-bottom: 2rem; + color: white; +} + +.overall-content { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.overall-label { + display: block; + font-size: 0.875rem; + opacity: 0.9; +} + +.overall-value { + font-size: 2.5rem; + font-weight: 700; +} + +.completed-badge { + text-align: right; +} + +.completed-count { + font-size: 1.5rem; + font-weight: 700; +} + +.completed-label { + font-size: 0.875rem; + opacity: 0.9; +} + +.overall-bar { + height: 8px; + background: rgba(255, 255, 255, 0.3); + border-radius: 4px; + overflow: hidden; +} + +.overall-bar-fill { + height: 100%; + background: white; + border-radius: 4px; + transition: width 0.5s ease; +} + +/* Games Grid */ +.games-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1.5rem; +} + +/* Game Card */ +.game-card { + background: white; + border-radius: 1rem; + padding: 1.5rem; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + border: 1px solid #e5e7eb; + transition: transform 0.2s, box-shadow 0.2s; +} + +.game-card:hover { + transform: translateY(-2px); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); +} + +.game-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1.25rem; + flex-wrap: wrap; +} + +.game-icon { + width: 52px; + height: 52px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + flex-shrink: 0; +} + +.game-icon img { + width: 100%; + height: 100%; + object-fit: contain; + padding: 6px; +} + +/* Adjust filter for logos that need better visibility */ +.tft-icon img { + filter: brightness(0) invert(1); +} + +.lol-icon img { + filter: brightness(0) invert(1); +} + +.rl-icon img { + filter: none; +} + +.faceit-icon img { + filter: brightness(0) invert(1); +} + +.tft-icon { + background: linear-gradient(135deg, #92400e 0%, #78350f 100%); + box-shadow: 0 4px 12px rgba(146, 64, 14, 0.4); +} + +.lol-icon { + background: linear-gradient(135deg, #0ac8b9 0%, #0a7b72 100%); + box-shadow: 0 4px 12px rgba(10, 200, 185, 0.4); +} + +.rl-icon { + background: linear-gradient(135deg, #0078d7 0%, #005a9e 100%); + box-shadow: 0 4px 12px rgba(0, 120, 215, 0.4); +} + +.faceit-icon { + background: linear-gradient(135deg, #ff5500 0%, #cc4400 100%); + box-shadow: 0 4px 12px rgba(255, 85, 0, 0.4); +} + +.faceit-icon img { + padding: 8px; +} + +.game-info { + flex: 1; + min-width: 120px; +} + +.game-title { + font-size: 1rem; + font-weight: 600; + color: #1f2937; + margin: 0; +} + +.game-rank { + font-size: 0.875rem; + color: #6b7280; +} + +.hot-streak-badge, .elo-badge { + font-size: 0.75rem; + padding: 0.25rem 0.75rem; + border-radius: 9999px; + font-weight: 600; +} + +.hot-streak-badge { + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + color: #92400e; +} + +.elo-badge { + background: linear-gradient(135deg, #fed7aa 0%, #fdba74 100%); + color: #9a3412; +} + +/* Progress Section */ +.progress-section { + margin-bottom: 1.25rem; +} + +.progress-labels { + display: flex; + justify-content: space-between; + font-size: 0.875rem; + color: #6b7280; + margin-bottom: 0.5rem; +} + +.progress-percent { + font-weight: 600; + color: #1f2937; +} + +.progress-bar { + height: 8px; + background: #e5e7eb; + border-radius: 4px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + border-radius: 4px; + transition: width 0.5s ease; +} + +.tft-fill { background: linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%); } +.lol-fill { background: linear-gradient(90deg, #3b82f6 0%, #1d4ed8 100%); } +.rl-fill { background: linear-gradient(90deg, #10b981 0%, #059669 100%); } +.faceit-fill { background: linear-gradient(90deg, #f97316 0%, #ea580c 100%); } + +/* Stats Grid */ +.stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0.75rem; + margin-bottom: 1rem; + padding: 1rem; + background: #f9fafb; + border-radius: 0.75rem; +} + +.stat-item { + text-align: center; +} + +.stat-value { + display: block; + font-size: 1.125rem; + font-weight: 700; + color: #1f2937; +} + +.stat-label { + font-size: 0.75rem; + color: #6b7280; +} + +/* Streak Banner */ +.streak-banner { + padding: 0.5rem 1rem; + border-radius: 0.5rem; + font-size: 0.875rem; + font-weight: 600; + text-align: center; + margin-bottom: 1rem; +} + +.win-streak { + background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%); + color: #166534; +} + +.loss-streak { + background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); + color: #991b1b; +} + +/* Matches Section */ +.matches-section { + border-top: 1px solid #e5e7eb; + padding-top: 1rem; +} + +.matches-title { + font-size: 0.875rem; + font-weight: 600; + color: #374151; + margin: 0 0 0.75rem 0; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.estimated-tag { + font-size: 0.625rem; + font-weight: 500; + color: #9ca3af; + background: #f3f4f6; + padding: 0.125rem 0.375rem; + border-radius: 4px; +} + +.lp-change { + font-size: 0.75rem; + font-weight: 600; + padding: 0.125rem 0.375rem; + border-radius: 4px; + margin-left: 0.25rem; +} + +.lp-change.positive { + color: #166534; + background: rgba(34, 197, 94, 0.15); +} + +.lp-change.negative { + color: #991b1b; + background: rgba(239, 68, 68, 0.15); +} + +/* Promotion/Demotion styles */ +.match-item.promotion { + border-left: 3px solid #fbbf24 !important; + background: linear-gradient(135deg, #fefce8 0%, #fef9c3 100%) !important; +} + +.match-item.demotion { + border-left: 3px solid #8b5cf6 !important; + background: linear-gradient(135deg, #faf5ff 0%, #f3e8ff 100%) !important; +} + +.rank-change-badge { + font-size: 0.7rem; + font-weight: 600; + padding: 0.2rem 0.5rem; + border-radius: 4px; + margin-left: 0.25rem; + white-space: nowrap; +} + +.promotion-badge { + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + color: #92400e; + border: 1px solid #fbbf24; +} + +.demotion-badge { + background: linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%); + color: #5b21b6; + border: 1px solid #a78bfa; +} + +.match-item.promotion:hover { + background: linear-gradient(135deg, #fef9c3 0%, #fef08a 100%) !important; +} + +.match-item.demotion:hover { + background: linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 100%) !important; +} + +.matches-list-container { + max-height: 280px; + overflow-y: auto; + border-radius: 0.5rem; + scrollbar-width: thin; + scrollbar-color: #d1d5db #f3f4f6; +} + +.matches-list-container::-webkit-scrollbar { + width: 6px; +} + +.matches-list-container::-webkit-scrollbar-track { + background: #f3f4f6; + border-radius: 3px; +} + +.matches-list-container::-webkit-scrollbar-thumb { + background: #d1d5db; + border-radius: 3px; +} + +.matches-list-container::-webkit-scrollbar-thumb:hover { + background: #9ca3af; +} + +.matches-list { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.load-more-btn { + width: 100%; + margin-top: 0.75rem; + padding: 0.625rem 1rem; + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); + border: 1px solid #d1d5db; + border-radius: 0.5rem; + font-size: 0.8rem; + font-weight: 500; + color: #4b5563; + cursor: pointer; + transition: all 0.2s ease; +} + +.load-more-btn:hover { + background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%); + border-color: #9ca3af; + color: #1f2937; +} + +.load-more-btn:active { + transform: scale(0.98); +} + +.match-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0.75rem; + border-radius: 0.5rem; + font-size: 0.875rem; + background: #f9fafb; +} + +.match-item.win { + background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%); + border-left: 3px solid #22c55e; +} + +.match-item.loss { + background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%); + border-left: 3px solid #ef4444; +} + +.match-main { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.match-result { + font-weight: 600; +} + +.match-item.win .match-result { color: #166534; } +.match-item.loss .match-result { color: #991b1b; } + +.match-champion, .match-score { + color: #6b7280; +} + +.match-details { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.match-kda { + font-weight: 500; + color: #374151; +} + +.match-date { + color: #9ca3af; + font-size: 0.75rem; +} + +.match-right { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.match-link-icon { + font-size: 0.875rem; + color: #9ca3af; + transition: color 0.2s, transform 0.2s; +} + +.match-item.clickable { + text-decoration: none; + cursor: pointer; + transition: transform 0.15s, box-shadow 0.15s; +} + +.match-item.clickable:hover { + transform: translateX(4px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.match-item.clickable:hover .match-link-icon { + color: #6b7280; + transform: translate(2px, -2px); +} + +.match-item.win.clickable:hover { + background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%); +} + +.match-item.loss.clickable:hover { + background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); +} + +/* SSR Notice */ +.ssr-notice { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + background: #f3f4f6; + border-radius: 0.5rem; + font-size: 0.875rem; + color: #6b7280; +} + +/* No Data State */ +.no-data { + text-align: center; + padding: 4rem 2rem; +} + +.no-data-icon { + font-size: 3rem; + display: block; + margin-bottom: 1rem; +} + +.no-data-text { + font-size: 1.25rem; + color: #374151; + margin: 0 0 0.5rem 0; +} + +.no-data-hint { + color: #9ca3af; + margin: 0; +} + +/* New Stats Grid */ +.stats-grid-new { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; +} + +.stat-item-new { + text-align: center; + padding: 1rem; + background: #f9fafb; + border-radius: 0.5rem; +} + +.stat-value-new { + display: block; + font-size: 1.5rem; + font-weight: 700; + color: #1f2937; +} + +.stat-label-new { + font-size: 0.75rem; + color: #6b7280; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* Chart Center Text */ +.chart-center-text { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + pointer-events: none; + z-index: 10; +} + +.center-text-main { + font-size: 1.25rem; + font-weight: 700; + color: #1f2937; + line-height: 1.2; + margin-bottom: 0.25rem; +} + +.center-text-lp { + font-size: 0.875rem; + font-weight: 500; + color: #6b7280; + line-height: 1; +} + +/* Responsive */ +@media (max-width: 640px) { + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .stats-grid-new { + grid-template-columns: repeat(2, 1fr); + } + + .overall-value { + font-size: 2rem; + } +} diff --git a/src/app/features/gaming/gaming-goals.component.ts b/src/app/features/gaming/gaming-goals.component.ts new file mode 100644 index 0000000..3321e7e --- /dev/null +++ b/src/app/features/gaming/gaming-goals.component.ts @@ -0,0 +1,1286 @@ +import { Component, OnInit, OnDestroy, signal, computed, effect, inject } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { CommonModule } from '@angular/common'; +import { NgChartsModule } from 'ng2-charts'; +import { + ChartConfiguration, + ChartData, + Chart, +} from 'chart.js'; +import { GoalsService } from '../../core/services/goals.service'; +import { RiotService } from '../../core/services/riot.service'; +import { FaceitService } from '../../core/services/faceit.service'; +import { + GamingProgress, + GamingGoal, + Match, + RankInfo, +} from '../../core/models/gaming-goal.model'; +import { environment } from '../../../environments/environment'; + +// Register Chart.js (required for ng2-charts) +Chart.register(); + +@Component({ + selector: 'app-gaming-goals', + standalone: true, + imports: [CommonModule, NgChartsModule], + styleUrls: ['./gaming-goals.component.scss'], + template: ` +
+ +
+

Gaming Goals

+

Track your climb to the top in 2026

+
+ + + @if (loading()) { +
+
+

Fetching your stats...

+
+ } + + + @if (!loading() && progress()) { + +
+
+
+ Overall Progress + {{ overallProgress().toFixed(1) }}% +
+
+
+ {{ completedGoals() }} + / 4 goals +
+
+
+
+
+
+
+ + +
+ +
+
+
+ TFT +
+
+

Teamfight Tactics

+

Current: {{ progress()!.tft.current }} | Target: {{ progress()!.tft.target }}

+
+ @if (progress()!.tft.rankInfo?.hotStreak) { + 🔥 Hot Streak! + } +
+ +
+
+ +
+

Progress

+
+ +
+
{{ tftCenterText() }}
+ @if (progress()?.tft?.rankInfo && progress()!.tft.rankInfo?.tier !== 'UNRANKED') { +
{{ progress()!.tft.rankInfo!.leaguePoints }} LP
+ } +
+
+
+

+ {{ progress()!.tft.progress.toFixed(1) }}% Progress +

+

+ Target: {{ progress()!.tft.target }} +

+
+
+ + +
+

Statistics

+ @if (progress()!.tft.rankInfo) { +
+
+ {{ progress()!.tft.rankInfo!.leaguePoints }} + LP +
+
+ {{ progress()!.tft.stats?.winRate?.toFixed(1) || 0 }}% + Win Rate +
+
+ {{ progress()!.tft.stats?.totalGames || 0 }} + Games +
+ @if (progress()!.tft.stats?.avgPlacement) { +
+ {{ progress()!.tft.stats!.avgPlacement!.toFixed(1) }} + Avg Place +
+ } +
+ } + + @if (progress()!.tft.stats?.streak && progress()!.tft.stats!.streak.count > 0) { +
+ {{ progress()!.tft.stats!.streak.count }} Game {{ progress()!.tft.stats!.streak.type === 'win' ? 'Win' : 'Loss' }} Streak +
+ } +
+
+ + @if (tftMatches().length > 0) { + + } +
+
+ + +
+
+
+ League of Legends +
+
+

League of Legends

+

Current: {{ progress()!.lol.current }} | Target: {{ progress()!.lol.target }}

+
+ @if (progress()!.lol.rankInfo?.hotStreak) { + 🔥 Hot Streak! + } +
+ +
+
+ +
+

Progress

+
+ +
+
{{ lolCenterText() }}
+ @if (progress()?.lol?.rankInfo && progress()!.lol.rankInfo?.tier !== 'UNRANKED') { +
{{ progress()!.lol.rankInfo!.leaguePoints }} LP
+ } +
+
+
+

+ {{ progress()!.lol.progress.toFixed(1) }}% Progress +

+

+ Target: {{ progress()!.lol.target }} +

+
+
+ + +
+

Statistics

+ @if (progress()!.lol.rankInfo) { +
+
+ {{ progress()!.lol.rankInfo!.leaguePoints }} + LP +
+
+ {{ progress()!.lol.stats?.winRate?.toFixed(1) || 0 }}% + Win Rate +
+
+ {{ progress()!.lol.rankInfo!.wins }} + Wins +
+
+ {{ progress()!.lol.rankInfo!.losses }} + Losses +
+
+ } + + @if (progress()!.lol.stats?.streak && progress()!.lol.stats!.streak.count > 0) { +
+ {{ progress()!.lol.stats!.streak.count }} Game {{ progress()!.lol.stats!.streak.type === 'win' ? 'Win' : 'Loss' }} Streak +
+ } +
+
+ + @if (lolMatches().length > 0) { + + } +
+
+ + +
+
+
+ Rocket League +
+
+

Rocket League

+

Current: {{ progress()!.rocketLeague.current }} | Target: {{ progress()!.rocketLeague.target }}

+
+
+ +
+
+ +
+

Progress

+
+ +
+
{{ rlCenterText() }}
+
+
+
+

+ {{ progress()!.rocketLeague.progress.toFixed(1) }}% Progress +

+

+ Target: {{ progress()!.rocketLeague.target }} +

+
+
+ + +
+

Statistics

+ @if (progress()!.rocketLeague.stats) { +
+
+ {{ progress()!.rocketLeague.stats!.winRate?.toFixed(1) || 0 }}% + Win Rate +
+
+ {{ progress()!.rocketLeague.stats!.totalGames || 0 }} + Games +
+
+ } +
+ ℹ️ + Match history requires SSR +
+
+
+
+
+ + +
+
+
+ Faceit +
+
+

Faceit CS2

+

Current: {{ progress()!.faceit.current }} | Target: {{ progress()!.faceit.target }}

+
+ @if (progress()!.faceit.stats?.elo) { + {{ progress()!.faceit.stats!.elo }} ELO + } +
+ +
+
+ +
+

Progress

+
+ +
+
{{ faceitCenterText() }}
+ @if (progress()?.faceit?.stats?.elo) { +
{{ progress()!.faceit.stats!.elo }} ELO
+ } +
+
+
+

+ {{ progress()!.faceit.progress.toFixed(1) }}% Progress +

+

+ Target: {{ progress()!.faceit.target }} +

+
+
+ + +
+

Statistics

+ @if (progress()!.faceit.stats) { +
+
+ {{ progress()!.faceit.stats!.winRate?.toFixed(1) || 0 }}% + Win Rate +
+
+ {{ progress()!.faceit.stats!.totalGames || 0 }} + Games +
+
+ {{ progress()!.faceit.stats!.kda?.toFixed(2) || 'N/A' }} + KDA +
+
+ {{ progress()!.faceit.stats!.adr?.toFixed(0) || 'N/A' }} + ADR +
+
+ } + + @if (progress()!.faceit.stats?.streak && progress()!.faceit.stats!.streak.count > 0) { +
+ {{ progress()!.faceit.stats!.streak.count }} Game {{ progress()!.faceit.stats!.streak.type === 'win' ? 'Win' : 'Loss' }} Streak +
+ } +
+
+ + @if (faceitMatches().length > 0) { + + } +
+
+
+ } + + + @if (!loading() && !progress()) { +
+ 🎮 +

No data available

+

Configure API keys in environment.ts

+
+ } +
+ `, +}) +export class GamingGoalsComponent implements OnInit, OnDestroy { + private readonly destroy$ = new Subject(); + private readonly goalsService = inject(GoalsService); + private readonly riotService = inject(RiotService); + private readonly faceitService = inject(FaceitService); + + readonly loading = signal(true); + readonly progress = signal(null); + + // Store matches separately for each game (initialized from progress, then extended via Load More) + readonly tftMatches = signal([]); + readonly lolMatches = signal([]); + readonly faceitMatches = signal([]); + + // Store puuid/playerId for fetching more matches + private tftPuuid: string | null = null; + private lolPuuid: string | null = null; + private faceitPlayerId: string | null = null; + + // Track loading state for Load More buttons + readonly loadingMoreTft = signal(false); + readonly loadingMoreLol = signal(false); + readonly loadingMoreFaceit = signal(false); + + readonly Math = Math; + + readonly overallProgress = computed(() => { + const p = this.progress(); + if (!p) return 0; + return (p.tft.progress + p.lol.progress + p.rocketLeague.progress + p.faceit.progress) / 4; + }); + + readonly completedGoals = computed(() => { + const p = this.progress(); + if (!p) return 0; + return [p.tft, p.lol, p.rocketLeague, p.faceit] + .filter((g) => g.progress >= 100).length; + }); + + constructor() { + // Watch the cached signal - data should already be loaded by app component + effect(() => { + const progress = this.goalsService.gamingProgress$(); + if (progress) { + this.progress.set(progress); + // Initialize matches from progress (first 5) + this.tftMatches.set(progress.tft.recentMatches || []); + this.lolMatches.set(progress.lol.recentMatches || []); + this.faceitMatches.set(progress.faceit.recentMatches || []); + this.loading.set(false); + // Fetch puuid/playerId for Load More functionality + this.#fetchIdentifiers(); + } else if (!this.loading()) { + // If no cached data and not already loading, load it (should rarely happen) + this.loadData(); + } + }); + } + + /** + * Fetch puuid/playerId for Load More functionality + */ + #fetchIdentifiers(): void { + // Fetch TFT puuid + this.riotService.getTFTPuuid().pipe(takeUntil(this.destroy$)).subscribe({ + next: (puuid) => { + if (puuid) this.tftPuuid = puuid; + }, + }); + + // Fetch LoL puuid + this.riotService.getLoLPuuid().pipe(takeUntil(this.destroy$)).subscribe({ + next: (puuid) => { + if (puuid) this.lolPuuid = puuid; + }, + }); + + // Fetch Faceit playerId + this.faceitService.getFaceitPlayerId().pipe(takeUntil(this.destroy$)).subscribe({ + next: (playerId) => { + if (playerId) this.faceitPlayerId = playerId; + }, + }); + } + + ngOnInit(): void { + // Check if data is already available + const cachedProgress = this.goalsService.gamingProgress$(); + if (cachedProgress) { + this.progress.set(cachedProgress); + this.loading.set(false); + } else { + // If not cached, load it (but this should rarely happen as app component preloads) + this.loadData(); + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + loadData(): void { + // Prevent multiple simultaneous calls + if (this.loading()) { + return; + } + + this.loading.set(true); + this.goalsService.loadGamingGoals().pipe( + takeUntil(this.destroy$) + ).subscribe({ + next: (progress) => { + this.progress.set(progress); + this.loading.set(false); + }, + error: () => { + this.loading.set(false); + }, + }); + } + + formatMatchDate(date: Date): string { + if (!date) return ''; + const d = new Date(date); + const now = new Date(); + const diffMs = now.getTime() - d.getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMins / 60); + const diffDays = Math.floor(diffHours / 24); + + if (diffMins < 60) return `${diffMins}m ago`; + if (diffHours < 24) return `${diffHours}h ago`; + if (diffDays < 7) return `${diffDays}d ago`; + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); + } + + loadMoreTft(): void { + if (this.loadingMoreTft()) return; + + this.loadingMoreTft.set(true); + const currentCount = this.tftMatches().length; + + // Get puuid if not cached + if (!this.tftPuuid) { + this.riotService.getTFTPuuid().pipe(takeUntil(this.destroy$)).subscribe({ + next: (puuid) => { + if (puuid) { + this.tftPuuid = puuid; + this.#loadMoreTftMatches(puuid, currentCount); + } else { + this.loadingMoreTft.set(false); + } + }, + error: () => { + this.loadingMoreTft.set(false); + }, + }); + } else { + this.#loadMoreTftMatches(this.tftPuuid, currentCount); + } + } + + loadMoreLol(): void { + if (this.loadingMoreLol()) return; + + this.loadingMoreLol.set(true); + const currentCount = this.lolMatches().length; + + // Get puuid if not cached + if (!this.lolPuuid) { + this.riotService.getLoLPuuid().pipe(takeUntil(this.destroy$)).subscribe({ + next: (puuid) => { + if (puuid) { + this.lolPuuid = puuid; + this.#loadMoreLolMatches(puuid, currentCount); + } else { + this.loadingMoreLol.set(false); + } + }, + error: () => { + this.loadingMoreLol.set(false); + }, + }); + } else { + this.#loadMoreLolMatches(this.lolPuuid, currentCount); + } + } + + loadMoreFaceit(): void { + if (this.loadingMoreFaceit()) return; + + this.loadingMoreFaceit.set(true); + const currentCount = this.faceitMatches().length; + + // Get playerId if not cached + if (!this.faceitPlayerId) { + this.faceitService.getFaceitPlayerId().pipe(takeUntil(this.destroy$)).subscribe({ + next: (playerId) => { + if (playerId) { + this.faceitPlayerId = playerId; + this.#loadMoreFaceitMatches(playerId, currentCount); + } else { + this.loadingMoreFaceit.set(false); + } + }, + error: () => { + this.loadingMoreFaceit.set(false); + }, + }); + } else { + this.#loadMoreFaceitMatches(this.faceitPlayerId, currentCount); + } + } + + #loadMoreTftMatches(puuid: string, startIndex: number): void { + this.riotService.getMoreTFTMatches(puuid, startIndex, 5) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (newMatches) => { + this.tftMatches.update((matches) => [...matches, ...newMatches]); + this.loadingMoreTft.set(false); + }, + error: () => { + this.loadingMoreTft.set(false); + }, + }); + } + + #loadMoreLolMatches(puuid: string, startIndex: number): void { + this.riotService.getMoreLoLMatches(puuid, startIndex, 5) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (newMatches) => { + this.lolMatches.update((matches) => [...matches, ...newMatches]); + this.loadingMoreLol.set(false); + }, + error: () => { + this.loadingMoreLol.set(false); + }, + }); + } + + #loadMoreFaceitMatches(playerId: string, startIndex: number): void { + this.faceitService.getMoreFaceitMatches(playerId, startIndex, 5) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (newMatches) => { + this.faceitMatches.update((matches) => [...matches, ...newMatches]); + this.loadingMoreFaceit.set(false); + }, + error: () => { + this.loadingMoreFaceit.set(false); + }, + }); + } + + // Doughnut chart data as computed signals with rank tier segments + readonly tftDoughnutData = computed((): ChartData<'doughnut'> => { + const p = this.progress(); + if (!p || !p.tft.rankInfo) { + return { labels: [], datasets: [] }; + } + return this.#buildRankTierChart(p.tft.rankInfo, 'Diamond', '#fbbf24'); + }); + + readonly lolDoughnutData = computed((): ChartData<'doughnut'> => { + const p = this.progress(); + if (!p || !p.lol.rankInfo) { + return { labels: [], datasets: [] }; + } + return this.#buildRankTierChart(p.lol.rankInfo, 'Diamond', '#0ac8b9'); + }); + + readonly rlDoughnutData = computed((): ChartData<'doughnut'> => { + const p = this.progress(); + if (!p) { + return { labels: [], datasets: [] }; + } + const progress = p.rocketLeague.progress; + const remaining = Math.max(0, 100 - progress); + return { + labels: ['Progress', 'Remaining'], + datasets: [ + { + label: 'Progress', + data: [progress, remaining], + backgroundColor: ['#0078d7', 'rgba(156, 163, 175, 0.15)'], + borderWidth: 4, + borderColor: '#ffffff', + borderRadius: 8, + }, + ], + }; + }); + + readonly faceitDoughnutData = computed((): ChartData<'doughnut'> => { + const p = this.progress(); + if (!p || !p.faceit.stats?.elo) { + return { labels: [], datasets: [] }; + } + return this.#buildFaceitLevelChart(p.faceit.stats.elo, '#ff5500'); + }); + + // Doughnut chart options + readonly doughnutOptions: ChartConfiguration<'doughnut'>['options'] = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: true, + callbacks: { + label: (context) => { + const label = context.label || ''; + const value = context.parsed || 0; + return `${label}: ${value.toFixed(1)}%`; + }, + }, + }, + }, + cutout: '70%', + }; + + /** + * Get center text for LoL chart (current division) + */ + readonly lolCenterText = computed(() => { + const p = this.progress(); + if (!p || !p.lol.rankInfo || p.lol.rankInfo.tier === 'UNRANKED') { + return 'Unranked'; + } + const rank = p.lol.rankInfo.rank || 'IV'; + return `${p.lol.rankInfo.tier} ${rank}`; + }); + + /** + * Get center text for TFT chart (current division) + */ + readonly tftCenterText = computed(() => { + const p = this.progress(); + if (!p || !p.tft.rankInfo || p.tft.rankInfo.tier === 'UNRANKED') { + return 'Unranked'; + } + const rank = p.tft.rankInfo.rank || 'IV'; + return `${p.tft.rankInfo.tier} ${rank}`; + }); + + /** + * Get center text for Rocket League chart (current rank) + */ + readonly rlCenterText = computed(() => { + const p = this.progress(); + if (!p || !p.rocketLeague.current || p.rocketLeague.current === 'Unranked') { + return 'Unranked'; + } + return p.rocketLeague.current; + }); + + /** + * Get center text for Faceit chart (current level) + */ + readonly faceitCenterText = computed(() => { + const p = this.progress(); + if (!p || !p.faceit.current || p.faceit.current === 'Unranked') { + return 'Unranked'; + } + return p.faceit.current; + }); + + /** + * Build doughnut chart with rank tier segments for LoL/TFT + */ + #buildRankTierChart( + rankInfo: RankInfo, + targetTier: string, + primaryColor: string + ): ChartData<'doughnut'> { + const tierOrder = [ + 'IRON', + 'BRONZE', + 'SILVER', + 'GOLD', + 'PLATINUM', + 'EMERALD', + 'DIAMOND', + 'MASTER', + 'GRANDMASTER', + 'CHALLENGER', + ]; + + const targetIndex = tierOrder.indexOf(targetTier.toUpperCase()); + const currentIndex = tierOrder.indexOf(rankInfo.tier); + + if (currentIndex < 0 || targetIndex < 0) { + return { labels: [], datasets: [] }; + } + + // If already at or above target, show 100% complete + if (currentIndex >= targetIndex) { + return { + labels: [`${rankInfo.tier} - Target Reached!`], + datasets: [ + { + label: 'Rank Progress', + data: [100], + backgroundColor: [primaryColor], + borderWidth: 2, + borderColor: '#ffffff', + borderRadius: 4, + }, + ], + }; + } + + const labels: string[] = []; + const data: number[] = []; + const colors: string[] = []; + + // Division order (IV is lowest, I is highest) + const divisionOrder = ['IV', 'III', 'II', 'I']; + + // Calculate total divisions from current tier to target + // Each tier has 4 divisions, so we need to count divisions + const totalDivisions = (targetIndex - currentIndex) * 4; + + // Get current division index (IV=0, III=1, II=2, I=3) + const currentDivisionIndex = divisionOrder.indexOf(rankInfo.rank || 'IV'); + const currentDivision = rankInfo.rank || 'IV'; + + // Each division represents equal progress (100% / total divisions to target) + const divisionProgress = 100 / totalDivisions; + + // Add segments for completed tiers (all 4 divisions each) + for (let i = 0; i < currentIndex && i <= targetIndex; i++) { + const tierName = tierOrder[i]; + const tierColor = this.#getTierColor(tierName, primaryColor, true); + // Add all 4 divisions for completed tiers + for (const div of divisionOrder) { + labels.push(`${tierName} ${div}`); + data.push(divisionProgress); + colors.push(tierColor); + } + } + + // Add segments for current tier divisions + if (currentIndex <= targetIndex) { + const tierName = rankInfo.tier; + const tierColor = this.#getTierColor(tierName, primaryColor, true); + const dimmedColor = this.#getTierColor(tierName, primaryColor, false); + + // Add completed divisions in current tier (divisions below current) + for (let i = 0; i < currentDivisionIndex; i++) { + labels.push(`${tierName} ${divisionOrder[i]}`); + data.push(divisionProgress); + colors.push(tierColor); + } + + // Add current division with LP progress + const lpInDivision = Math.min(rankInfo.leaguePoints, 100); + const currentDivisionProgress = (lpInDivision / 100) * divisionProgress; + const remainingInDivision = divisionProgress - currentDivisionProgress; + + labels.push(`${tierName} ${currentDivision} (${rankInfo.leaguePoints} LP)`); + data.push(currentDivisionProgress); + colors.push(tierColor); + + // Add remaining in current division + if (remainingInDivision > 0.1) { + labels.push(`${tierName} ${currentDivision} remaining`); + data.push(remainingInDivision); + colors.push(dimmedColor); + } + + // Add remaining divisions in current tier (divisions above current) + for (let i = currentDivisionIndex + 1; i < 4; i++) { + labels.push(`${tierName} ${divisionOrder[i]}`); + data.push(divisionProgress); + colors.push(dimmedColor); + } + } + + // Add segments for remaining tiers (all 4 divisions each) + for (let i = currentIndex + 1; i < targetIndex; i++) { + const tierName = tierOrder[i]; + const tierColor = this.#getTierColor(tierName, primaryColor, false); + // Add all 4 divisions for remaining tiers + for (const div of divisionOrder) { + labels.push(`${tierName} ${div}`); + data.push(divisionProgress); + colors.push(tierColor); + } + } + + // Create border widths array - thicker for achieved segments + const borderWidths = colors.map((color, index) => { + // Check if this is an achieved segment (not dimmed) + const isAchieved = !color.includes('rgba') || parseFloat(color.split(',')[3]?.trim() || '1') > 0.5; + return isAchieved ? 4 : 1; // Thicker border for achieved, thinner for unachieved + }); + + return { + labels, + datasets: [ + { + label: 'Rank Progress', + data, + backgroundColor: colors, + borderWidth: borderWidths, + borderColor: '#ffffff', + borderRadius: 4, + }, + ], + }; + } + + /** + * Build doughnut chart with level segments for Faceit + * Uses actual ELO ranges (levels are not equally split) + */ + #buildFaceitLevelChart(currentElo: number, primaryColor: string): ChartData<'doughnut'> { + // Faceit ELO thresholds for each level (not equal ranges) + const eloThresholds = [0, 501, 751, 901, 1051, 1201, 1351, 1531, 1751, 2001]; + const targetElo = 2001; + const totalEloRange = targetElo; // 0 to 2001 + + // If already at or above target, show 100% complete + if (currentElo >= targetElo) { + return { + labels: ['Level 10 - Target Reached!'], + datasets: [ + { + label: 'Level Progress', + data: [100], + backgroundColor: [primaryColor], + borderWidth: 2, + borderColor: '#ffffff', + borderRadius: 4, + }, + ], + }; + } + + const labels: string[] = []; + const data: number[] = []; + const colors: string[] = []; + + // Find current level + let currentLevel = 1; + for (let i = eloThresholds.length - 1; i >= 0; i--) { + if (currentElo >= eloThresholds[i]) { + currentLevel = i + 1; + break; + } + } + + // Add segments for completed levels (using actual ELO ranges) + for (let i = 1; i < currentLevel && i <= 10; i++) { + const levelStartElo = eloThresholds[i - 1]; + const levelEndElo = eloThresholds[i]; + const levelEloRange = levelEndElo - levelStartElo; + // Calculate percentage based on actual ELO range + const levelProgressPercent = (levelEloRange / totalEloRange) * 100; + + labels.push(`Level ${i} (${levelStartElo}-${levelEndElo - 1} ELO)`); + data.push(levelProgressPercent); + colors.push(this.#getLevelColor(i, primaryColor, true)); + } + + // Add segment for current level (with ELO progress) + if (currentLevel <= 10) { + const levelStartElo = eloThresholds[currentLevel - 1]; + const levelEndElo = currentLevel < 10 ? eloThresholds[currentLevel] : targetElo; + const levelEloRange = levelEndElo - levelStartElo; + const eloInLevel = currentElo - levelStartElo; + + // Calculate progress within current level + const levelProgressPercent = (levelEloRange / totalEloRange) * 100; + const currentProgressPercent = levelEloRange > 0 + ? (eloInLevel / levelEloRange) * levelProgressPercent + : 0; + const remainingInLevel = levelProgressPercent - currentProgressPercent; + + // Show current level with ELO and range + if (currentLevel < 10) { + const levelEndElo = eloThresholds[currentLevel] - 1; + labels.push(`Level ${currentLevel} (${currentElo}/${levelEndElo} ELO)`); + data.push(currentProgressPercent); + colors.push(this.#getLevelColor(currentLevel, primaryColor, true)); + + if (remainingInLevel > 0.1) { + labels.push(`Level ${currentLevel} remaining (${currentElo + 1}-${levelEndElo} ELO)`); + data.push(remainingInLevel); + colors.push(this.#getLevelColor(currentLevel, primaryColor, false)); + } + } else { + // Level 10 + labels.push(`Level ${currentLevel} (${currentElo}/2001+ ELO)`); + data.push(currentProgressPercent); + colors.push(this.#getLevelColor(currentLevel, primaryColor, true)); + } + } + + // Add segments for remaining levels (using actual ELO ranges) + for (let i = currentLevel + 1; i <= 10; i++) { + const levelStartElo = eloThresholds[i - 1]; + const levelEndElo = i < 10 ? eloThresholds[i] : targetElo; + const levelEloRange = levelEndElo - levelStartElo; + // Calculate percentage based on actual ELO range + const levelProgressPercent = (levelEloRange / totalEloRange) * 100; + + // For level 10, show 2001+ instead of range + if (i === 10) { + labels.push(`Level ${i} (2001+ ELO)`); + } else { + labels.push(`Level ${i} (${levelStartElo}-${levelEndElo - 1} ELO)`); + } + data.push(levelProgressPercent); + colors.push(this.#getLevelColor(i, primaryColor, false)); + } + + // Create border widths array - thicker for achieved segments + const borderWidths = colors.map((color, index) => { + // Check if this is an achieved segment (not dimmed) + const isAchieved = !color.includes('rgba') || parseFloat(color.split(',')[3]?.trim() || '1') > 0.5; + return isAchieved ? 4 : 1; // Thicker border for achieved, thinner for unachieved + }); + + return { + labels, + datasets: [ + { + label: 'Level Progress', + data, + backgroundColor: colors, + borderWidth: borderWidths, + borderColor: '#ffffff', + borderRadius: 4, + }, + ], + }; + } + + /** + * Get actual rank color for LoL/TFT tiers + */ + #getTierColor(tier: string, primaryColor: string, completed: boolean): string { + // Official League of Legends rank colors (official palette) + const tierColors: Record = { + 'IRON': '#5A5A5A', // Dark Iron Gray + 'BRONZE': '#CD7F32', // Bronze + 'SILVER': '#C7D0D8', // Silver + 'GOLD': '#FFD700', // Gold + 'PLATINUM': '#3FE0C5', // Teal / Aqua + 'EMERALD': '#2ECC71', // Emerald Green + 'DIAMOND': '#6FA8FF', // Light Blue + 'MASTER': '#9C2CFF', // Vivid Purple + 'GRANDMASTER': '#D91E36', // Crimson Red + 'CHALLENGER': '#FFD966', // Radiant Gold + }; + + const color = tierColors[tier.toUpperCase()] || primaryColor; + + if (completed) { + // Achieved tiers: full opacity and slightly brighter + return this.#brightenColor(color, 1.1); + } + // Remaining tiers: much more dimmed + return this.#addOpacity(color, 0.08); + } + + /** + * Get actual rank color for Faceit levels + */ + #getLevelColor(level: number, primaryColor: string, completed: boolean): string { + // Faceit level colors (based on their official color scheme) + const levelColors: Record = { + 1: '#8B4513', // Brown (Level 1) + 2: '#808080', // Gray (Level 2) + 3: '#C0C0C0', // Light Gray (Level 3) + 4: '#90EE90', // Light Green (Level 4) + 5: '#00FF00', // Green (Level 5) + 6: '#00CED1', // Dark Turquoise (Level 6) + 7: '#0000FF', // Blue (Level 7) + 8: '#8A2BE2', // Blue Violet (Level 8) + 9: '#FF8C00', // Dark Orange (Level 9) + 10: '#FF5500', // Orange (Level 10 - Faceit brand color) + }; + + const color = levelColors[level] || primaryColor; + + if (completed) { + // Achieved levels: full opacity and slightly brighter + return this.#brightenColor(color, 1.1); + } + // Remaining levels: much more dimmed + return this.#addOpacity(color, 0.08); + } + + /** + * Helper to add opacity to a hex color + */ + #addOpacity(hex: string, opacity: number): string { + // Remove # if present + const hexClean = hex.replace('#', ''); + + // Convert to RGB + const r = parseInt(hexClean.substring(0, 2), 16); + const g = parseInt(hexClean.substring(2, 4), 16); + const b = parseInt(hexClean.substring(4, 6), 16); + + return `rgba(${r}, ${g}, ${b}, ${opacity})`; + } + + /** + * Helper to brighten a hex color + */ + #brightenColor(hex: string, factor: number): string { + // Remove # if present + const hexClean = hex.replace('#', ''); + + // Convert to RGB + let r = parseInt(hexClean.substring(0, 2), 16); + let g = parseInt(hexClean.substring(2, 4), 16); + let b = parseInt(hexClean.substring(4, 6), 16); + + // Brighten by factor (but cap at 255) + r = Math.min(255, Math.round(r * factor)); + g = Math.min(255, Math.round(g * factor)); + b = Math.min(255, Math.round(b * factor)); + + // Convert back to hex + return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; + } +} diff --git a/src/app/features/sport/overall-progress-section/overall-progress-section.component.ts b/src/app/features/sport/overall-progress-section/overall-progress-section.component.ts new file mode 100644 index 0000000..898438e --- /dev/null +++ b/src/app/features/sport/overall-progress-section/overall-progress-section.component.ts @@ -0,0 +1,163 @@ +import { Component, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NgChartsModule } from 'ng2-charts'; +import { + ChartConfiguration, + ChartData, +} from 'chart.js'; +import { SportProgress } from '../../../core/models/sport-goal.model'; +import { ChartCardComponent } from '../../../shared/components/chart-card/chart-card.component'; + +@Component({ + selector: 'app-overall-progress-section', + standalone: true, + imports: [CommonModule, NgChartsModule, ChartCardComponent], + template: ` +
+
+ 📊 +
+

Overall Progress

+

2026 year overview and combined sports progress

+
+
+ + +
+ +
+

+ 📅 + 2026 Year Progress +

+
+
+ + + +
+ +
+
+
+
+ {{ progress.overallPercentage.toFixed(1) }}% +
+
Actual
+
+
+
+ +
+
+
+ Actual: {{ progress.overallPercentage.toFixed(1) }}% +
+
+
+ Optimal: {{ idealProgress.toFixed(1) }}% +
+
+

+ ⏱️ {{ daysElapsed }} / {{ totalDaysInYear }} days +

+
+ + {{ daysBehind.toFixed(0) }} day{{ daysBehind !== 1 ? 's' : '' }} behind schedule + + + 🚀 {{ Math.abs(daysBehind).toFixed(0) }} day{{ Math.abs(daysBehind) !== 1 ? 's' : '' }} ahead of schedule + + + Right on schedule! + +
+
+
+ + +
+
+
+
+

+ 🎯 + Sports Progress +

+
+
+ +
+
+
+ {{ progress.overallPercentage.toFixed(1) }}% +
+
Overall
+
+ +
+
+
+ Bike: {{ progress.bike.percentage.toFixed(1) }}% +
+
+
+ Run: {{ progress.run.percentage.toFixed(1) }}% +
+
+
+ Swim: {{ progress.swim.percentage.toFixed(1) }}% +
+
+
+
+
+
+ + + +
+ `, +}) +export class OverallProgressSectionComponent { + @Input() progress!: SportProgress; + @Input() yearProgress = 0; + @Input() idealProgress = 0; + @Input() daysElapsed = 0; + @Input() totalDaysInYear = 365; + @Input() daysBehind: number | null = null; + @Input() isOnTrack = false; + @Input() yearProgressDoughnutData!: ChartData<'doughnut'>; + @Input() yearProgressOptimalDoughnutData!: ChartData<'doughnut'>; + @Input() sportsProgressBikeDoughnutData!: ChartData<'doughnut'>; + @Input() combinedChartData!: ChartData<'line'>; + @Input() yearProgressDoughnutOptions!: ChartConfiguration<'doughnut'>['options']; + @Input() yearProgressOptimalDoughnutOptions!: ChartConfiguration<'doughnut'>['options']; + @Input() sportsProgressBikeDoughnutOptions!: ChartConfiguration<'doughnut'>['options']; + @Input() progressChartOptions!: ChartConfiguration['options']; + + readonly Math = Math; +} + diff --git a/src/app/features/sport/sport-goals.component.scss b/src/app/features/sport/sport-goals.component.scss new file mode 100644 index 0000000..c649b78 --- /dev/null +++ b/src/app/features/sport/sport-goals.component.scss @@ -0,0 +1,17 @@ +// Component styles if needed + + + + + + + + + + + + + + + + diff --git a/src/app/features/sport/sport-goals.component.ts b/src/app/features/sport/sport-goals.component.ts new file mode 100644 index 0000000..6ee4865 --- /dev/null +++ b/src/app/features/sport/sport-goals.component.ts @@ -0,0 +1,1159 @@ +import { Component, OnInit, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { GoalsService } from '../../core/services/goals.service'; +import { StravaService } from '../../core/services/strava.service'; +import { SportProgress, SportGoal, Activity } from '../../core/models/sport-goal.model'; +import { ProgressCardComponent } from '../../shared/components/progress-card/progress-card.component'; +import { ChartCardComponent } from '../../shared/components/chart-card/chart-card.component'; +import { + ChartConfiguration, + ChartData, + Chart, +} from 'chart.js'; +import zoomPlugin from 'chartjs-plugin-zoom'; +import ChartDataLabels from 'chartjs-plugin-datalabels'; +import { NgChartsModule, BaseChartDirective } from 'ng2-charts'; + +// Register plugins +Chart.register(zoomPlugin, ChartDataLabels); +import { getIdealProgress, getDaysElapsedIn2026, getTotalDaysIn2026 } from '../../shared/utils/date.utils'; +import { environment } from '../../../environments/environment'; +import { OverallProgressSectionComponent } from './overall-progress-section/overall-progress-section.component'; +import { SportSectionComponent } from './sport-section/sport-section.component'; + +@Component({ + selector: 'app-sport-goals', + standalone: true, + imports: [CommonModule, OverallProgressSectionComponent, SportSectionComponent], + template: ` +
+
+

+ 🏆 + Sport Goals +

+

+ 📊 + Track your progress toward 2026 fitness goals +

+
+ +
+

Loading...

+
+ +
+ + + + + + + + + + + + +
+ +
+
🔗
+

+ 📊 + Please connect your Strava account to track your sport goals. +

+ +
+
+ `, + styles: [ + ` + @keyframes fade-in { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + .animate-fade-in { + animation: fade-in 0.6s ease-out; + } + @keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } + } + .animate-shimmer { + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.2) 50%, + transparent 100% + ); + background-size: 200% 100%; + animation: shimmer 5s infinite; + } + `, + ], +}) +export class SportGoalsComponent implements OnInit { + progress: SportProgress | null = null; + loading = true; + isOnTrack = false; + isAuthenticated = false; + readonly hasEnvironmentToken = !!environment.strava.accessToken; + yearProgress = 0; + daysElapsed = 0; + totalDaysInYear = 365; + daysBehind: number | null = null; + idealProgress = 0; + + combinedChartData: ChartData<'line'> = { + labels: [], + datasets: [], + }; + + bikeChartData: ChartData<'line'> = { + labels: [], + datasets: [], + }; + + runChartData: ChartData<'line'> = { + labels: [], + datasets: [], + }; + + swimChartData: ChartData<'line'> = { + labels: [], + datasets: [], + }; + + overallDoughnutData: ChartData<'doughnut'> = { + labels: [], + datasets: [], + }; + + bikeDoughnutData: ChartData<'doughnut'> = { + labels: [], + datasets: [], + }; + + runDoughnutData: ChartData<'doughnut'> = { + labels: [], + datasets: [], + }; + + swimDoughnutData: ChartData<'doughnut'> = { + labels: [], + datasets: [], + }; + + yearProgressDoughnutData: ChartData<'doughnut'> = { + labels: [], + datasets: [], + }; + + yearProgressOptimalDoughnutData: ChartData<'doughnut'> = { + labels: [], + datasets: [], + }; + + sportsProgressBikeDoughnutData: ChartData<'doughnut'> = { + labels: [], + datasets: [], + }; + + yearProgressDoughnutOptions: ChartConfiguration<'doughnut'>['options'] = { + responsive: true, + maintainAspectRatio: false, + cutout: '70%', + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: false, + }, + datalabels: { + display: false, + }, + }, + elements: { + arc: { + borderRadius: 8, + borderJoinStyle: 'round', + }, + }, + }; + + yearProgressOptimalDoughnutOptions: ChartConfiguration<'doughnut'>['options'] = { + responsive: true, + maintainAspectRatio: false, + cutout: '60%', + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: false, + }, + datalabels: { + display: false, + }, + }, + elements: { + arc: { + borderRadius: 8, + borderJoinStyle: 'round', + }, + }, + }; + + overallProgressDoughnutOptions: ChartConfiguration<'doughnut'>['options'] = { + responsive: true, + maintainAspectRatio: false, + cutout: '65%', + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: false, + }, + }, + elements: { + arc: { + borderWidth: 0, + borderRadius: 8, + borderJoinStyle: 'round', + }, + }, + }; + + overallProgressOptimalDoughnutOptions: ChartConfiguration<'doughnut'>['options'] = { + responsive: true, + maintainAspectRatio: false, + cutout: '80%', + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: false, + }, + }, + elements: { + arc: { + borderWidth: 0, + borderRadius: 8, + borderJoinStyle: 'round', + }, + }, + }; + + sportsProgressBikeDoughnutOptions: ChartConfiguration<'doughnut'>['options'] = { + responsive: true, + maintainAspectRatio: false, + cutout: '60%', + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: false, + }, + datalabels: { + display: false, + }, + }, + elements: { + arc: { + borderRadius: 8, + borderJoinStyle: 'round', + }, + }, + }; + + + doughnutChartOptions: ChartConfiguration<'doughnut'>['options'] = { + responsive: true, + maintainAspectRatio: false, + cutout: '60%', + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: false, + }, + datalabels: { + display: false, + }, + }, + elements: { + arc: { + borderRadius: 8, + borderJoinStyle: 'round', + }, + }, + }; + + progressChartOptions: ChartConfiguration['options'] = { + responsive: true, + maintainAspectRatio: false, + animation: { + duration: 0, + }, + plugins: { + legend: { + display: true, + position: 'top', + }, + datalabels: { + display: false, + }, + tooltip: { + mode: 'index', + intersect: false, + callbacks: { + label: (context) => { + const dataset = context.dataset; + const percentage = context.parsed.y; + const target = (dataset as any).target as number; + const label = dataset.label || ''; + const chart = context.chart; + + if (percentage === null || percentage === undefined) { + return ''; + } + + // Check if this is the combined chart (has "Overall Progress" dataset) + const isCombinedChart = chart.data.datasets.some( + (ds: any) => ds.label === 'Overall Progress' + ); + + // For overall progress, only show percentage + if (label === 'Overall Progress') { + return `${label}: ${percentage.toFixed(1)}%`; + } + + // For optimal progress: show km in individual charts, only % in combined chart + if (label === 'Optimal Progress') { + if (isCombinedChart) { + return `${label}: ${percentage.toFixed(1)}%`; + } else { + // Individual chart - show km and percentage + const kilometers = (percentage / 100) * target; + return `${label}: ${kilometers.toFixed(2)} km (${percentage.toFixed(1)}%)`; + } + } + + // For individual sports, show both km and percentage + const kilometers = (percentage / 100) * target; + return `${label}: ${kilometers.toFixed(2)} km (${percentage.toFixed(1)}%)`; + }, + }, + }, + zoom: { + zoom: { + wheel: { + enabled: true, + }, + pinch: { + enabled: true, + }, + mode: 'x', + }, + pan: { + enabled: true, + mode: 'x', + }, + limits: { + y: { + min: 0, + max: 100, + }, + }, + }, + }, + scales: { + y: { + beginAtZero: true, + min: 0, + max: 100, + title: { + display: true, + text: 'Progress (%)', + }, + afterDataLimits: (scale) => { + // Always ensure y-axis starts at 0 + scale.min = 0; + // Use setTimeout to ensure this runs after zoom operations + setTimeout(() => { + this.adjustYAxisMaxFromScale(scale); + }, 0); + }, + afterUpdate: (scale) => { + // Also adjust after scale updates (like when zooming) + scale.min = 0; + this.adjustYAxisMaxFromScale(scale); + }, + }, + x: { + title: { + display: true, + text: 'Month', + }, + ticks: { + maxRotation: 45, + minRotation: 45, + }, + }, + }, + interaction: { + mode: 'nearest', + axis: 'x', + intersect: false, + }, + }; + + readonly Math = Math; + + private readonly goalsService = inject(GoalsService); + private readonly stravaService = inject(StravaService); + + ngOnInit(): void { + this.isAuthenticated = this.stravaService.isAuthenticated(); + this.calculateYearProgress(); + this.idealProgress = getIdealProgress(); + // Always try to load data - service will handle empty response if not authenticated + this.loadData(); + } + + private calculateYearProgress(): void { + this.daysElapsed = getDaysElapsedIn2026(); + this.totalDaysInYear = getTotalDaysIn2026(); + this.yearProgress = Math.min(100, (this.daysElapsed / this.totalDaysInYear) * 100); + } + + loadData(): void { + this.loading = true; + this.idealProgress = getIdealProgress(); + this.calculateYearProgress(); + this.goalsService.loadSportGoals().subscribe({ + next: (progress) => { + this.progress = progress; + this.isOnTrack = this.goalsService.isOnTrackForSport(progress); + this.calculateDaysBehind(progress); + this.updateCharts(progress); + this.loading = false; + }, + error: () => { + this.loading = false; + }, + }); + } + + private calculateDaysBehind(progress: SportProgress): void { + const ideal = getIdealProgress(); + const actual = progress.overallPercentage; + const difference = ideal - actual; + + // Calculate days behind based on percentage difference + // If 1% = (totalDays / 100) days, then difference% = (difference / 100) * totalDays + this.daysBehind = Math.round((difference / 100) * this.totalDaysInYear); + } + + /** + * Calculate days ahead or behind schedule for an individual sport + * @param goal The sport goal to calculate for + * @returns Positive number if behind, negative if ahead, 0 if on schedule + */ + getDaysAheadOrBehind(goal: SportGoal): number { + const ideal = getIdealProgress(); + const actual = goal.percentage; + const difference = ideal - actual; + + // Calculate days ahead/behind based on percentage difference + // If 1% = (totalDays / 100) days, then difference% = (difference / 100) * totalDays + return Math.round((difference / 100) * this.totalDaysInYear); + } + + /** + * Get the last 3 activities for a sport, sorted by date (most recent first) + * Filters out activities with 0 distance or very short durations + * @param goal The sport goal to get activities from + * @returns Array of up to 3 most recent valid activities + */ + getRecentActivities(goal: SportGoal): Activity[] { + if (!goal.activities || goal.activities.length === 0) { + return []; + } + + // Filter out invalid activities: + // - Must have distance > 0.1 km (100 meters) to be considered a real training + // - Must match the sport type + // - Must have duration > 0 (or at least some moving time) + const validActivities = goal.activities.filter((activity) => { + const distanceKm = activity.distance / 1000; + const matchesType = activity.type === goal.type; + const hasValidDistance = distanceKm >= 0.1; // At least 100 meters + const hasValidTime = activity.movingTime > 0; + + return matchesType && hasValidDistance && hasValidTime; + }); + + if (validActivities.length === 0) { + return []; + } + + // Sort activities by startDate (most recent first) + const sorted = [...validActivities].sort((a, b) => { + const dateA = typeof a.startDate === 'string' ? new Date(a.startDate) : a.startDate; + const dateB = typeof b.startDate === 'string' ? new Date(b.startDate) : b.startDate; + return dateB.getTime() - dateA.getTime(); + }); + + // Return last 3 + return sorted.slice(0, 3); + } + + getEstimatedCompletionDate(goal: SportGoal): string { + if (goal.percentage >= 100) { + return 'Completed!'; + } + + if (goal.current === 0 || this.daysElapsed === 0) { + return 'N/A'; + } + + // Calculate average km per day so far + const avgKmPerDay = goal.current / this.daysElapsed; + + if (avgKmPerDay <= 0) { + return 'N/A'; + } + + // Calculate remaining km + const remainingKm = goal.target - goal.current; + + // Calculate days needed at current rate + const daysNeeded = remainingKm / avgKmPerDay; + + // Calculate completion date + const now = new Date(); + const completionDate = new Date(now); + completionDate.setDate(completionDate.getDate() + Math.ceil(daysNeeded)); + + // Check if completion date is beyond end of year + const endOfYear = new Date('2026-12-31'); + if (completionDate > endOfYear) { + return 'Beyond 2026'; + } + + return completionDate.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }); + } + + getDailyKmNeeded(goal: SportGoal): number { + if (goal.percentage >= 100) { + return 0; + } + + // Calculate remaining km + const remainingKm = goal.target - goal.current; + + // Calculate days remaining in year + const now = new Date(); + const endOfYear = new Date('2026-12-31'); + const daysRemaining = Math.max( + 1, + Math.ceil((endOfYear.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)) + ); + + // Calculate daily km needed + return remainingKm / daysRemaining; + } + + getCurrentDailyAvg(goal: SportGoal): number { + if (this.daysElapsed === 0) { + return 0; + } + return goal.current / this.daysElapsed; + } + + getOptimalDailyAvg(goal: SportGoal): number { + return goal.target / this.totalDaysInYear; + } + + connectStrava(): void { + this.stravaService.initiateOAuth(); + } + + formatDistance(km: number): string { + return km.toFixed(2); + } + + private rgbToRgba(rgb: string, alpha: number): string { + const match = rgb.match(/\d+/g); + if (!match || match.length < 3) { + return `rgba(59, 130, 246, ${alpha})`; + } + return `rgba(${match[0]}, ${match[1]}, ${match[2]}, ${alpha})`; + } + + private setInitialZoom(): void { + // This will be called after chart initialization + // We'll set initial zoom in the chart-card component or use a different approach + } + + private adjustYAxisMaxFromScale(scale: any): void { + if (!scale || !scale.chart) { + return; + } + + const chart = scale.chart; + const xScale = chart.scales?.['x']; + + if (!xScale || !chart.data || !chart.data.datasets) { + return; + } + + // Always ensure min is 0 + scale.min = 0; + + // Check if zoomed (x-axis range is smaller than full range) + const fullRange = chart.data.labels.length; + const visibleRange = xScale.max - xScale.min; + const isZoomed = visibleRange < fullRange * 0.95; // More lenient check + + if (!isZoomed) { + // Not zoomed, use default max of 100 + scale.max = 100; + return; + } + + // Get the visible range on x-axis (use integer indices) + const minIndex = Math.max(0, Math.floor(xScale.min)); + const maxIndex = Math.min( + chart.data.labels.length - 1, + Math.ceil(xScale.max) + ); + + // Find max value in visible range across all datasets + let maxValue = 0; + chart.data.datasets.forEach((dataset: any) => { + const data = dataset.data as (number | null)[]; + for (let i = minIndex; i <= maxIndex; i++) { + const value = data[i]; + if (value !== null && value !== undefined && !isNaN(value) && value > maxValue) { + maxValue = value; + } + } + }); + + // Add 10% padding and ensure it doesn't exceed 100%, but at least show 10% + const paddedMax = Math.min(100, Math.max(maxValue * 1.1, 10)); + if (paddedMax > 0 && maxValue > 0) { + scale.max = paddedMax; + } else { + scale.max = 100; + } + } + + private updateCharts(progress: SportProgress): void { + this.combinedChartData = this.buildCombinedChart(progress); + this.bikeChartData = this.buildProgressChart( + progress.bike, + 'rgb(59, 130, 246)' + ); + this.runChartData = this.buildProgressChart( + progress.run, + 'rgb(34, 197, 94)' + ); + this.swimChartData = this.buildProgressChart( + progress.swim, + 'rgb(251, 191, 36)' + ); + + // Update doughnut charts with multiple datasets + // Year progress: optimal (outer) and actual (inner) as concentric rings + this.yearProgressOptimalDoughnutData = this.buildSingleRingDoughnutChart( + this.idealProgress, + 'rgba(156, 163, 175, 0.6)' + ); + this.yearProgressDoughnutData = this.buildSingleRingDoughnutChart( + progress.overallPercentage, + 'rgb(59, 130, 246)' + ); + + // Sports progress: bike, run, swim side by side + this.sportsProgressBikeDoughnutData = this.buildMultiDatasetDoughnutChart( + [ + { value: progress.bike.percentage, color: 'rgb(59, 130, 246)', label: 'Bike' }, + { value: progress.run.percentage, color: 'rgb(34, 197, 94)', label: 'Run' }, + { value: progress.swim.percentage, color: 'rgb(251, 191, 36)', label: 'Swim' }, + ] + ); + // Individual sport doughnut charts: actual and optimal side by side + this.bikeDoughnutData = this.buildMultiDatasetDoughnutChart( + [ + { value: progress.bike.percentage, color: 'rgb(59, 130, 246)', label: 'Actual' }, + { value: getIdealProgress(), color: 'rgba(156, 163, 175, 0.6)', label: 'Optimal' }, + ] + ); + this.runDoughnutData = this.buildMultiDatasetDoughnutChart( + [ + { value: progress.run.percentage, color: 'rgb(34, 197, 94)', label: 'Actual' }, + { value: getIdealProgress(), color: 'rgba(156, 163, 175, 0.6)', label: 'Optimal' }, + ] + ); + this.swimDoughnutData = this.buildMultiDatasetDoughnutChart( + [ + { value: progress.swim.percentage, color: 'rgb(251, 191, 36)', label: 'Actual' }, + { value: getIdealProgress(), color: 'rgba(156, 163, 175, 0.6)', label: 'Optimal' }, + ] + ); + } + + /** + * Build a single ring doughnut chart + */ + private buildSingleRingDoughnutChart( + value: number, + color: string + ): ChartData<'doughnut'> { + const remaining = Math.max(0, 100 - value); + return { + labels: ['Progress', 'Remaining'], + datasets: [ + { + label: 'Progress', + data: [value, remaining], + backgroundColor: [ + color, + 'rgba(156, 163, 175, 0.15)', + ], + borderWidth: 4, + borderColor: '#ffffff', + borderRadius: 8, + }, + ], + }; + } + + /** + * Build a multi-dataset doughnut chart (rings side by side) + */ + private buildMultiDatasetDoughnutChart( + datasets: Array<{ value: number; color: string; label: string }> + ): ChartData<'doughnut'> { + return { + datasets: datasets.map((dataset) => { + const remaining = Math.max(0, 100 - dataset.value); + return { + label: dataset.label, + data: [dataset.value, remaining], + backgroundColor: [ + dataset.color, + 'rgba(156, 163, 175, 0.15)', + ], + borderWidth: 4, + borderColor: '#ffffff', + borderRadius: 8, + }; + }), + }; + } + + private buildDoughnutChart( + actual: number, + optimal: number, + label: string, + color: string = 'rgb(168, 85, 247)' + ): ChartData<'doughnut'> { + // Show actual progress and remaining + const actualRemaining = Math.max(0, 100 - actual); + + return { + labels: ['Actual Progress', 'Remaining'], + datasets: [ + { + label: 'Progress', + data: [actual, actualRemaining], + backgroundColor: [ + color, + 'rgba(156, 163, 175, 0.2)', + ], + borderWidth: 0, + }, + ], + }; + } + + private buildCombinedChart(progress: SportProgress): ChartData<'line'> { + const allWeeks = this.getWeekLabels(); + const monthLabels = this.getMonthLabelsForWeeks(); + const optimalProgress = this.calculateOptimalProgress(); + const bikeProgress = this.calculateActualProgress(progress.bike); + const runProgress = this.calculateActualProgress(progress.run); + const swimProgress = this.calculateActualProgress(progress.swim); + + // Calculate overall progress percentage for each week (average of all three sports) + const overallProgress: number[] = [0]; // Week 0 starts at 0% + const maxLength = Math.max( + bikeProgress.length, + runProgress.length, + swimProgress.length + ); + + // Start from index 1 since index 0 is week 0 (already added) + for (let i = 1; i < maxLength; i++) { + const bikePct = i < bikeProgress.length ? bikeProgress[i] : 0; + const runPct = i < runProgress.length ? runProgress[i] : 0; + const swimPct = i < swimProgress.length ? swimProgress[i] : 0; + + // Average of the three percentages + const overallPercentage = (bikePct + runPct + swimPct) / 3; + overallProgress.push(Math.min(100, overallPercentage)); + } + + // Pad overall progress with null for future weeks + const paddedOverall: (number | null)[] = [...overallProgress]; + while (paddedOverall.length < allWeeks.length) { + paddedOverall.push(null); + } + + return { + labels: monthLabels, + datasets: [ + { + label: 'Optimal Progress', + data: optimalProgress, + borderColor: 'rgb(156, 163, 175)', + backgroundColor: 'rgba(156, 163, 175, 0.1)', + borderWidth: 2, + borderDash: [5, 5], + tension: 0.4, + pointRadius: 0, + spanGaps: false, + }, + { + label: 'Overall Progress', + data: paddedOverall, + borderColor: 'rgb(168, 85, 247)', + backgroundColor: this.rgbToRgba('rgb(168, 85, 247)', 0.1), + borderWidth: 2, + tension: 0.4, + pointRadius: 2, + pointHoverRadius: 4, + spanGaps: false, + ...({ target: null } as any), // No target for average calculation + }, + ], + }; + } + + private buildProgressChart( + goal: SportGoal, + color: string + ): ChartData<'line'> { + const allWeeks = this.getWeekLabels(); + const monthLabels = this.getMonthLabelsForWeeks(); + const optimalProgress = this.calculateOptimalProgress(); + const actualProgress = this.calculateActualProgress(goal); + + // Pad actual progress with null for future weeks (Chart.js will skip these points) + const paddedActualProgress: (number | null)[] = [...actualProgress]; + while (paddedActualProgress.length < allWeeks.length) { + paddedActualProgress.push(null); + } + + const target = goal.target; + + return { + labels: monthLabels, + datasets: [ + { + label: 'Optimal Progress', + data: optimalProgress, + borderColor: 'rgb(156, 163, 175)', + backgroundColor: 'rgba(156, 163, 175, 0.1)', + borderWidth: 2, + borderDash: [5, 5], + tension: 0.4, + pointRadius: 0, + spanGaps: false, + ...({ target } as any), // Store target for tooltip km calculation + }, + { + label: 'Actual Progress', + data: paddedActualProgress, + borderColor: color, + backgroundColor: this.rgbToRgba(color, 0.1), + borderWidth: 2, + tension: 0.4, + pointRadius: 2, + pointHoverRadius: 4, + spanGaps: false, + ...({ target } as any), + }, + ], + }; + } + + /** + * Get week boundaries for the entire year + * Returns array of {start, end, daysInWeek} for each week + */ + private getWeekBoundaries(): Array<{ start: Date; end: Date; daysInWeek: number }> { + const startOfYear = new Date('2026-01-01'); + const endOfYear = new Date('2026-12-31'); + const weeks: Array<{ start: Date; end: Date; daysInWeek: number }> = []; + + let weekStart = new Date(startOfYear); + + while (weekStart <= endOfYear) { + // Calculate week end (6 days after start, or end of year, whichever comes first) + const weekEnd = new Date(weekStart); + weekEnd.setDate(weekEnd.getDate() + 6); + + // If week end exceeds year end, cap it at year end + const actualWeekEnd = weekEnd > endOfYear ? new Date(endOfYear) : weekEnd; + + // Calculate actual days in this week + const daysInWeek = Math.floor( + (actualWeekEnd.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24) + ) + 1; + + weeks.push({ + start: new Date(weekStart), + end: actualWeekEnd, + daysInWeek, + }); + + // Move to next week + weekStart.setDate(weekStart.getDate() + 7); + } + + return weeks; + } + + private getWeekLabels(): string[] { + const weeks = this.getWeekBoundaries(); + return ['W0', ...weeks.map(() => '')]; + } + + private getMonthLabelsForWeeks(): string[] { + const weeks = this.getWeekBoundaries(); + const labels: string[] = ['W0']; // Add week 0 label + + weeks.forEach((week, index) => { + const monthName = week.start.toLocaleDateString('en-US', { month: 'short' }); + labels.push(`${monthName} W${index + 1}`); + }); + + return labels; + } + + private getWeekLabelsForActual(): string[] { + const startOfYear = new Date('2026-01-01'); + const now = new Date(); + const endOfYear = new Date('2026-12-31'); + const currentDate = now > endOfYear ? endOfYear : now; + const weeks: string[] = []; + + let weekStart = new Date(startOfYear); + let weekNumber = 1; + + while (weekStart <= currentDate) { + weeks.push(`W${weekNumber}`); + + weekStart.setDate(weekStart.getDate() + 7); + weekNumber++; + } + + return weeks; + } + + /** + * Calculate optimal progress based on actual days elapsed + * Handles partial weeks correctly (first and last week may not be full 7 days) + * Current week counts only elapsed days, future weeks show full trajectory + */ + private calculateOptimalProgress(): number[] { + const startOfYear = new Date('2026-01-01'); + const endOfYear = new Date('2026-12-31'); + const now = new Date(); + const currentDate = now > endOfYear ? endOfYear : now; + const progress: number[] = [0]; // Week 0 starts at 0% + const weeks = this.getWeekBoundaries(); + + // Calculate total days by summing all week days to ensure accuracy + const totalDays = weeks.reduce((sum, week) => sum + week.daysInWeek, 0); + + let cumulativeDays = 0; + + for (let i = 0; i < weeks.length; i++) { + const week = weeks[i]; + let daysToAdd = 0; + + if (week.end <= currentDate) { + // Past week - count all days in the week + daysToAdd = week.daysInWeek; + } else if (week.start <= currentDate && week.end > currentDate) { + // Current week - count only days from week start to current date + daysToAdd = Math.floor( + (currentDate.getTime() - week.start.getTime()) / (1000 * 60 * 60 * 24) + ) + 1; + } else { + // Future week - count full week to show target trajectory + daysToAdd = week.daysInWeek; + } + + cumulativeDays += daysToAdd; + + // Ensure the last week shows exactly 100% + if (i === weeks.length - 1) { + progress.push(100); + } else { + progress.push(Math.min(100, (cumulativeDays / totalDays) * 100)); + } + } + + return progress; + } + + /** + * Calculate actual progress for a sport goal + * Uses week boundaries to correctly handle partial weeks + */ + private calculateActualProgress(goal: SportGoal): number[] { + const now = new Date(); + const startOfYear = new Date('2026-01-01'); + const endOfYear = new Date('2026-12-31'); + const target = goal.target; + const currentDate = now > endOfYear ? endOfYear : now; + + const progress: number[] = [0]; // Week 0 starts at 0% + const weeks = this.getWeekBoundaries(); + + for (const week of weeks) { + // Use current date if we're in the current week, otherwise use end of week + const endDate = week.end > currentDate ? currentDate : week.end; + + // Only process weeks up to and including the current week + if (week.start > currentDate) { + break; + } + + const activitiesInPeriod = goal.activities.filter((activity: Activity) => { + const activityDate = new Date(activity.startDate); + return activityDate >= startOfYear && activityDate <= endDate; + }); + + const cumulativeDistance = activitiesInPeriod.reduce( + (sum: number, activity: Activity) => sum + activity.distance / 1000, + 0 + ); + + progress.push(Math.min(100, (cumulativeDistance / target) * 100)); + } + + return progress; + } +} + diff --git a/src/app/features/sport/sport-section/sport-section.component.ts b/src/app/features/sport/sport-section/sport-section.component.ts new file mode 100644 index 0000000..432c594 --- /dev/null +++ b/src/app/features/sport/sport-section/sport-section.component.ts @@ -0,0 +1,110 @@ +import { Component, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NgChartsModule } from 'ng2-charts'; +import { + ChartConfiguration, + ChartData, +} from 'chart.js'; +import { SportGoal, Activity } from '../../../core/models/sport-goal.model'; +import { ProgressCardComponent } from '../../../shared/components/progress-card/progress-card.component'; +import { ChartCardComponent } from '../../../shared/components/chart-card/chart-card.component'; + +@Component({ + selector: 'app-sport-section', + standalone: true, + imports: [CommonModule, NgChartsModule, ProgressCardComponent, ChartCardComponent], + template: ` +
+
+ {{ emoji }} +
+

{{ title }}

+

{{ description }}

+
+
+
+ +
+
+
+

Progress

+
+ +
+
+

+ {{ goal.percentage.toFixed(1) }}% Actual +

+

+ Optimal: {{ idealProgress.toFixed(1) }}% +

+
+
+
+ +
+
+
+
+
+ `, +}) +export class SportSectionComponent { + @Input() title = ''; + @Input() emoji = ''; + @Input() description = ''; + @Input() goal!: SportGoal; + @Input() estimatedCompletion: string | null = null; + @Input() dailyKmNeeded: number | null = null; + @Input() currentDailyAvg: number | null = null; + @Input() optimalDailyAvg: number | null = null; + @Input() daysAheadOrBehind: number | null = null; + @Input() recentActivities: Activity[] | null = null; + @Input() idealProgress = 0; + @Input() doughnutData!: ChartData<'doughnut'>; + @Input() chartData!: ChartData<'line'>; + @Input() doughnutOptions!: ChartConfiguration<'doughnut'>['options']; + @Input() chartOptions!: ChartConfiguration['options']; + @Input() gradientClass = ''; + @Input() borderClass = ''; + @Input() borderColorClass = ''; + + readonly Math = Math; + + formatDistance(km: number): string { + return km.toFixed(2); + } +} + diff --git a/src/app/features/sport/strava-callback.component.ts b/src/app/features/sport/strava-callback.component.ts new file mode 100644 index 0000000..0cbcc8d --- /dev/null +++ b/src/app/features/sport/strava-callback.component.ts @@ -0,0 +1,91 @@ +import { Component, OnInit, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; +import { StravaService } from '../../core/services/strava.service'; + +@Component({ + selector: 'app-strava-callback', + standalone: true, + imports: [CommonModule], + template: ` +
+
+

Connecting to Strava...

+

{{ error }}

+

Successfully connected to Strava!

+
+
+ `, + styles: [], +}) +export class StravaCallbackComponent implements OnInit { + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly stravaService = inject(StravaService); + + loading = true; + error: string | null = null; + success = false; + + ngOnInit(): void { + this.route.queryParams.subscribe((params) => { + const code = params['code']; + const error = params['error']; + + if (error) { + this.error = 'Authorization failed. Please try again.'; + this.loading = false; + setTimeout(() => { + this.router.navigate(['/sport']); + }, 3000); + return; + } + + if (code) { + this.exchangeCodeForToken(code); + } else { + this.error = 'No authorization code received.'; + this.loading = false; + setTimeout(() => { + this.router.navigate(['/sport']); + }, 3000); + } + }); + } + + private exchangeCodeForToken(code: string): void { + this.stravaService.exchangeCodeForToken(code).subscribe({ + next: () => { + this.success = true; + this.loading = false; + setTimeout(() => { + this.router.navigate(['/sport']); + }, 2000); + }, + error: (err) => { + this.error = 'Failed to connect to Strava. Please try again.'; + this.loading = false; + console.error('Error exchanging code for token:', err); + setTimeout(() => { + this.router.navigate(['/sport']); + }, 3000); + }, + }); + } +} + + + + + + + + + + + + + + + + diff --git a/src/app/shared/components/animated-counter/animated-counter.component.ts b/src/app/shared/components/animated-counter/animated-counter.component.ts new file mode 100644 index 0000000..62fcdf5 --- /dev/null +++ b/src/app/shared/components/animated-counter/animated-counter.component.ts @@ -0,0 +1,71 @@ +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-animated-counter', + standalone: true, + imports: [CommonModule], + template: ` + + {{ displayValue }}{{ suffix }} + + `, + styles: [], +}) +export class AnimatedCounterComponent implements OnInit, OnDestroy { + @Input() value: number = 0; + @Input() duration: number = 2000; + @Input() decimals: number = 1; + @Input() suffix: string = ''; + @Input() color: string = ''; + + displayValue: number = 0; + private animationId: number | null = null; + private startTime: number = 0; + private startValue: number = 0; + + ngOnInit(): void { + this.startValue = this.displayValue; + this.startTime = performance.now(); + this.animate(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + } + + private animate = (): void => { + const currentTime = performance.now(); + const elapsed = currentTime - this.startTime; + const progress = Math.min(elapsed / this.duration, 1); + + // Easing function (ease-out) + const easeOut = 1 - Math.pow(1 - progress, 3); + + this.displayValue = this.startValue + (this.value - this.startValue) * easeOut; + + if (progress < 1) { + this.animationId = requestAnimationFrame(this.animate); + } else { + this.displayValue = this.value; + } + }; +} + + + + + + + + + + + + + + + + diff --git a/src/app/shared/components/basketball-game/basketball-game.component.ts b/src/app/shared/components/basketball-game/basketball-game.component.ts new file mode 100644 index 0000000..9ffd50e --- /dev/null +++ b/src/app/shared/components/basketball-game/basketball-game.component.ts @@ -0,0 +1,524 @@ +import { + Component, + OnInit, + OnDestroy, + ElementRef, + ViewChild, + AfterViewInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import * as THREE from 'three'; + +@Component({ + selector: 'app-basketball-game', + standalone: true, + imports: [CommonModule], + template: ` +
+ +
+ Score: {{ score }} +
+
+ `, + styles: [ + ` + .basketball-game-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100vw; + height: 100vh; + z-index: 50; + pointer-events: none; + touch-action: pan-y; + } + canvas { + display: block; + cursor: crosshair; + background: transparent; + width: 100vw; + height: 100vh; + pointer-events: auto; + touch-action: pan-y pinch-zoom; + } + .basketball-game-container > div { + pointer-events: auto; + } + `, + ], +}) +export class BasketballGameComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('canvas', { static: false }) canvasRef!: ElementRef; + + private scene!: THREE.Scene; + private camera!: THREE.OrthographicCamera; + private renderer!: THREE.WebGLRenderer; + private animationId: number | null = null; + private ball!: THREE.Mesh; + private hoop!: THREE.Group; + private isDragging = false; + private dragStart = new THREE.Vector2(); + private ballVelocity = new THREE.Vector2(); + private lastMouseX = 0; + private lastMouseY = 0; + private trajectory: THREE.Vector2[] = []; + private showTrajectory = false; + private trajectoryDots: THREE.Points[] = []; + score = 0; + private gravity = 0.4; + private bounce = 0.65; + private friction = 0.985; + private aspect = 1; + private readonly ballStartPos = { x: -15, y: 5 }; + + ngOnInit(): void {} + + ngAfterViewInit(): void { + this.initThree(); + this.createScene(); + this.setupEventListeners(); + this.animate(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + if (this.renderer) { + this.renderer.dispose(); + } + } + + private initThree(): void { + const canvas = this.canvasRef.nativeElement; + const width = window.innerWidth; + const height = window.innerHeight; + this.aspect = width / height; + + this.scene = new THREE.Scene(); + this.scene.background = null; + + // Orthographic camera for 2D + const viewSize = 20; + this.camera = new THREE.OrthographicCamera( + -viewSize * this.aspect, + viewSize * this.aspect, + viewSize, + -viewSize, + 0.1, + 1000 + ); + this.camera.position.z = 10; + + this.renderer = new THREE.WebGLRenderer({ + canvas, + alpha: true, + antialias: true, + }); + this.renderer.setSize(width, height); + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + + window.addEventListener('resize', () => this.onWindowResize()); + } + + private createScene(): void { + // Ball (2D circle) + const ballGeometry = new THREE.CircleGeometry(0.8, 32); + const ballMaterial = new THREE.MeshBasicMaterial({ + color: 0xff8c00, + }); + this.ball = new THREE.Mesh(ballGeometry, ballMaterial); + this.ball.position.set(this.ballStartPos.x, this.ballStartPos.y, 0); + this.scene.add(this.ball); + + // Add basketball lines (2D) + const lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 2 }); + + // Horizontal line + const hLineGeometry = new THREE.BufferGeometry().setFromPoints([ + new THREE.Vector3(-0.8, 0, 0.01), + new THREE.Vector3(0.8, 0, 0.01), + ]); + const hLine = new THREE.Line(hLineGeometry, lineMaterial); + this.ball.add(hLine); + + // Vertical line + const vLineGeometry = new THREE.BufferGeometry().setFromPoints([ + new THREE.Vector3(0, -0.8, 0.01), + new THREE.Vector3(0, 0.8, 0.01), + ]); + const vLine = new THREE.Line(vLineGeometry, lineMaterial); + this.ball.add(vLine); + + // Hoop (on the right side, 2D) - make it VERY visible with filled shapes and larger size + this.hoop = new THREE.Group(); + + // Backboard (filled rectangle for visibility - larger) + const backboardShape = new THREE.Shape(); + backboardShape.moveTo(17, 1.5); + backboardShape.lineTo(19, 1.5); + backboardShape.lineTo(19, 6.5); + backboardShape.lineTo(17, 6.5); + backboardShape.lineTo(17, 1.5); + const backboardGeometry = new THREE.ShapeGeometry(backboardShape); + const backboardMaterial = new THREE.MeshBasicMaterial({ + color: 0xffffff, + transparent: false, + }); + const backboard = new THREE.Mesh(backboardGeometry, backboardMaterial); + this.hoop.add(backboard); + + // Backboard outline (thick line) + const backboardLineGeometry = new THREE.BufferGeometry().setFromPoints([ + new THREE.Vector3(17, 1.5, 0.01), + new THREE.Vector3(19, 1.5, 0.01), + new THREE.Vector3(19, 6.5, 0.01), + new THREE.Vector3(17, 6.5, 0.01), + new THREE.Vector3(17, 1.5, 0.01), + ]); + const backboardLineMaterial = new THREE.LineBasicMaterial({ + color: 0x000000, + linewidth: 5, + }); + const backboardLine = new THREE.Line(backboardLineGeometry, backboardLineMaterial); + this.hoop.add(backboardLine); + + // Rim (filled semi-circle for visibility - larger) + const rimRadius = 1.5; // Increased from 1.2 + const rimShape = new THREE.Shape(); + for (let i = 0; i <= 64; i++) { + const angle = (i / 64) * Math.PI; + const x = 18 + Math.cos(angle) * rimRadius; + const y = 4 + Math.sin(angle) * rimRadius; + if (i === 0) { + rimShape.moveTo(x, y); + } else { + rimShape.lineTo(x, y); + } + } + // Close the shape + rimShape.lineTo(18 - rimRadius, 4); + rimShape.lineTo(18 + rimRadius, 4); + const rimGeometry = new THREE.ShapeGeometry(rimShape); + const rimMaterial = new THREE.MeshBasicMaterial({ + color: 0xff0000, // Bright red + transparent: false, + }); + const rim = new THREE.Mesh(rimGeometry, rimMaterial); + this.hoop.add(rim); + + // Rim outline (thick line - larger) + const rimPoints: THREE.Vector3[] = []; + for (let i = 0; i <= 64; i++) { + const angle = (i / 64) * Math.PI; + rimPoints.push(new THREE.Vector3(18 + Math.cos(angle) * rimRadius, 4 + Math.sin(angle) * rimRadius, 0.01)); + } + const rimLineGeometry = new THREE.BufferGeometry().setFromPoints(rimPoints); + const rimLineMaterial = new THREE.LineBasicMaterial({ + color: 0x000000, + linewidth: 5, + }); + const rimLine = new THREE.Line(rimLineGeometry, rimLineMaterial); + this.hoop.add(rimLine); + + // Net (vertical lines - more visible and larger) + const netMaterial = new THREE.LineBasicMaterial({ + color: 0xffffff, + linewidth: 4, + transparent: false, + }); + for (let i = 0; i < 12; i++) { + const x = 18 + Math.cos((i / 12) * Math.PI) * rimRadius; + const y = 4 + Math.sin((i / 12) * Math.PI) * rimRadius; + const netGeometry = new THREE.BufferGeometry().setFromPoints([ + new THREE.Vector3(x, y, 0.01), + new THREE.Vector3(x, y - 3, 0.01), + ]); + const netLine = new THREE.Line(netGeometry, netMaterial); + this.hoop.add(netLine); + } + + this.scene.add(this.hoop); + + // Trajectory will be rendered as dots + + // Floor line (subtle) + const floorGeometry = new THREE.BufferGeometry().setFromPoints([ + new THREE.Vector3(-20 * this.aspect, -8, 0), + new THREE.Vector3(20 * this.aspect, -8, 0), + ]); + const floorMaterial = new THREE.LineBasicMaterial({ + color: 0xffffff, + linewidth: 1, + transparent: true, + opacity: 0.3 + }); + const floor = new THREE.Line(floorGeometry, floorMaterial); + this.scene.add(floor); + } + + private setupEventListeners(): void { + const canvas = this.canvasRef.nativeElement; + + canvas.addEventListener('mousedown', (e) => this.onMouseDown(e)); + canvas.addEventListener('mousemove', (e) => this.onMouseMove(e)); + canvas.addEventListener('mouseup', (e) => this.onMouseUp(e)); + canvas.addEventListener('mouseleave', (e) => this.onMouseUp(e)); + + // Allow wheel events to pass through for scrolling + canvas.addEventListener('wheel', (e) => { + // Don't prevent default - allow scrolling + }, { passive: true }); + } + + private screenToWorld(x: number, y: number): THREE.Vector2 { + const canvas = this.canvasRef.nativeElement; + const rect = canvas.getBoundingClientRect(); + const mouseX = ((x - rect.left) / rect.width) * 2 - 1; + const mouseY = -((y - rect.top) / rect.height) * 2 + 1; + + const viewSize = 20; + return new THREE.Vector2( + mouseX * viewSize * this.aspect, + mouseY * viewSize + ); + } + + private onMouseDown(event: MouseEvent): void { + const worldPos = this.screenToWorld(event.clientX, event.clientY); + const distance = new THREE.Vector2( + worldPos.x - this.ball.position.x, + worldPos.y - this.ball.position.y + ).length(); + + // Allow dragging if clicking near the ball (slingshot style) + if (distance < 1.5) { + this.isDragging = true; + // Stop the ball immediately when clicked + this.ballVelocity.set(0, 0); + // Reset ball to starting position + this.ball.position.set(this.ballStartPos.x, this.ballStartPos.y, 0); + // Store the ball's starting position as the anchor point + this.dragStart.set(this.ballStartPos.x, this.ballStartPos.y); + this.showTrajectory = true; + } + } + + private onMouseMove(event: MouseEvent): void { + // Store last mouse position for onMouseUp + this.lastMouseX = event.clientX; + this.lastMouseY = event.clientY; + + if (this.isDragging) { + const worldPos = this.screenToWorld(event.clientX, event.clientY); + // Calculate drag vector (from ball start position to mouse) + const dragVector = new THREE.Vector2().subVectors(worldPos, this.dragStart); + const dragLength = dragVector.length(); + const maxDrag = 8; + + if (dragLength > maxDrag) { + dragVector.normalize().multiplyScalar(maxDrag); + } + + // Ball stays at starting position, only trajectory updates + this.ball.position.set(this.ballStartPos.x, this.ballStartPos.y, 0); + // Calculate trajectory based on drag vector + this.calculateTrajectory(dragVector); + } + } + + private onMouseUp(event?: MouseEvent): void { + if (this.isDragging) { + this.isDragging = false; + this.showTrajectory = false; + + // Get the final drag vector from last mouse position + let worldPos: THREE.Vector2; + if (event) { + worldPos = this.screenToWorld(event.clientX, event.clientY); + } else { + // Use stored last mouse position + worldPos = this.screenToWorld(this.lastMouseX, this.lastMouseY); + } + + // Clear trajectory dots + this.trajectoryDots.forEach(dot => { + this.scene.remove(dot); + dot.geometry.dispose(); + (dot.material as THREE.Material).dispose(); + }); + this.trajectoryDots = []; + + // Calculate velocity in opposite direction of drag (slingshot) + const dragVector = new THREE.Vector2().subVectors(worldPos, this.dragStart); + const dragLength = dragVector.length(); + + if (dragLength > 0.1) { + // Velocity is opposite to drag direction (slingshot effect) + const power = Math.min(dragLength * 0.15, 1.5); // Much slower speed + const direction = dragVector.normalize().multiplyScalar(-1); // Opposite direction + this.ballVelocity.copy(direction.multiplyScalar(power)); + } else { + this.ballVelocity.set(0, 0); + } + + // Ball is already at start position, no need to reset + } + } + + private calculateTrajectory(dragVector?: THREE.Vector2): void { + // Clear existing trajectory dots + this.trajectoryDots.forEach(dot => { + this.scene.remove(dot); + dot.geometry.dispose(); + (dot.material as THREE.Material).dispose(); + }); + this.trajectoryDots = []; + + // If dragVector is not provided, calculate it from current ball position + if (!dragVector) { + dragVector = new THREE.Vector2().subVectors( + new THREE.Vector2(this.ball.position.x, this.ball.position.y), + this.dragStart + ); + } + + const dragLength = dragVector.length(); + + if (dragLength < 0.1) { + return; + } + + // Calculate velocity in opposite direction (slingshot) + const power = Math.min(dragLength * 0.15, 1.5); // Much slower speed + const direction = dragVector.clone().normalize().multiplyScalar(-1); // Opposite direction + const velocity = direction.multiplyScalar(power); + + this.trajectory = []; + const pos = new THREE.Vector2(this.dragStart.x, this.dragStart.y); + const vel = new THREE.Vector2(velocity.x, velocity.y); + + // Sample trajectory points every few steps + for (let i = 0; i < 200; i++) { + if (i % 3 === 0) { // Only add every 3rd point for dots + this.trajectory.push(new THREE.Vector2(pos.x, pos.y)); + } + pos.add(vel); + vel.y -= this.gravity; + vel.multiplyScalar(this.friction); + + if (pos.y < -8 || Math.abs(pos.x) > 20) break; + } + + // Create dots for trajectory + this.trajectory.forEach((point, index) => { + const dotGeometry = new THREE.BufferGeometry().setFromPoints([ + new THREE.Vector3(point.x, point.y, 0) + ]); + const dotMaterial = new THREE.PointsMaterial({ + color: 0xffffff, + size: 0.3, + transparent: true, + opacity: 0.7, + }); + const dot = new THREE.Points(dotGeometry, dotMaterial); + this.scene.add(dot); + this.trajectoryDots.push(dot); + }); + } + + private updateBall(): void { + if (this.isDragging) return; + + const speed = this.ballVelocity.length(); + if (speed < 0.05) { + this.ballVelocity.set(0, 0); + // Reset ball to starting position when it stops + if (this.ball.position.x !== this.ballStartPos.x || this.ball.position.y !== this.ballStartPos.y) { + this.ball.position.set(this.ballStartPos.x, this.ballStartPos.y, 0); + } + return; + } + + this.ball.position.x += this.ballVelocity.x; + this.ball.position.y += this.ballVelocity.y; + this.ballVelocity.y -= this.gravity; + this.ballVelocity.multiplyScalar(this.friction); + + const viewSize = 20; + + // Bounce off floor + if (this.ball.position.y < -7.2) { + this.ball.position.y = -7.2; + this.ballVelocity.y *= -this.bounce; + this.ballVelocity.x *= 0.9; // Reduce horizontal velocity on bounce + if (Math.abs(this.ballVelocity.y) < 0.2) { + this.ballVelocity.y = 0; + // Reset ball position if it stops + if (Math.abs(this.ballVelocity.x) < 0.15) { + this.ball.position.set(this.ballStartPos.x, this.ballStartPos.y, 0); + this.ballVelocity.set(0, 0); + } + } + } + + // Bounce off walls + if (Math.abs(this.ball.position.x) > viewSize * this.aspect - 1) { + this.ballVelocity.x *= -this.bounce; + this.ball.position.x = Math.max( + -viewSize * this.aspect + 1, + Math.min(viewSize * this.aspect - 1, this.ball.position.x) + ); + } + + // Bounce off top + if (this.ball.position.y > viewSize - 1) { + this.ballVelocity.y *= -this.bounce; + this.ball.position.y = viewSize - 1; + } + + // Check basket (right side, around y=4) + // Ball must pass through the rim area while moving downward + const rimCenterX = 18; + const rimCenterY = 4; + const rimRadius = 1.5; // Match the visual rim radius + const distFromRimCenter = Math.sqrt( + Math.pow(this.ball.position.x - rimCenterX, 2) + + Math.pow(this.ball.position.y - rimCenterY, 2) + ); + + // Check if ball passes through the rim (within radius and moving downward) + if ( + distFromRimCenter < rimRadius + 0.5 && + this.ball.position.y > 3 && + this.ball.position.y < 5 && + this.ballVelocity.y < 0.5 && + this.ballVelocity.y > -3 + ) { + this.score++; + this.ballVelocity.set(0, 0); + this.ball.position.set(this.ballStartPos.x, this.ballStartPos.y, 0); + } + } + + private animate = (): void => { + this.animationId = requestAnimationFrame(this.animate); + this.updateBall(); + this.renderer.render(this.scene, this.camera); + }; + + private onWindowResize(): void { + const width = window.innerWidth; + const height = window.innerHeight; + this.aspect = width / height; + + const viewSize = 20; + this.camera.left = -viewSize * this.aspect; + this.camera.right = viewSize * this.aspect; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(width, height); + } +} diff --git a/src/app/shared/components/chart-card/chart-card.component.ts b/src/app/shared/components/chart-card/chart-card.component.ts new file mode 100644 index 0000000..eacfe95 --- /dev/null +++ b/src/app/shared/components/chart-card/chart-card.component.ts @@ -0,0 +1,145 @@ +import { + Component, + Input, + OnInit, + OnChanges, + SimpleChanges, + ViewChild, + AfterViewInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { + BaseChartDirective, + NgChartsModule, +} from 'ng2-charts'; +import { + ChartConfiguration, + ChartData, + ChartType, +} from 'chart.js'; + +@Component({ + selector: 'app-chart-card', + standalone: true, + imports: [CommonModule, NgChartsModule], + template: ` +
+

+ 📈 + {{ title }} +

+
+ +
+
+ `, + styles: [], +}) +export class ChartCardComponent implements OnInit, OnChanges, AfterViewInit { + @Input() title = ''; + @Input() chartData: ChartData = { datasets: [], labels: [] }; + @Input() chartType: ChartType = 'line'; + @Input() chartOptions: ChartConfiguration['options'] = {}; + + @ViewChild(BaseChartDirective) chart?: BaseChartDirective; + + mergedOptions: ChartConfiguration['options'] = {}; + + ngOnInit(): void { + this.updateOptions(); + } + + ngAfterViewInit(): void { + // Set initial zoom after chart is rendered + setTimeout(() => { + this.setInitialZoom(); + }, 200); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['chartOptions'] || changes['chartData']) { + this.updateOptions(); + if (this.chart) { + this.chart.update(); + setTimeout(() => { + this.setInitialZoom(); + }, 200); + } + } + } + + private setInitialZoom(): void { + if (!this.chart || !this.chart.chart) { + return; + } + + const chart = this.chart.chart; + const xScale = chart.scales?.['x']; + + if (!xScale || !chart.data || !chart.data.labels) { + return; + } + + // Calculate current week index + const now = new Date(); + const startOfYear = new Date('2026-01-01'); + const weeksSinceStart = Math.floor( + (now.getTime() - startOfYear.getTime()) / (1000 * 60 * 60 * 24 * 7) + ); + + // Show from week 0 to current week (add 1 because week 0 is included) + // If we're on week 3, we want to show weeks 0, 1, 2, 3 (4 weeks total) + const totalWeeks = chart.data.labels.length; + const startWeek = 0; + const endWeek = Math.min(totalWeeks - 1, weeksSinceStart + 1); + + // Use zoom plugin's zoomScale method to set initial zoom + const zoomPlugin = (chart as any).plugins?.plugins?.zoom; + if (zoomPlugin) { + // Try using the zoom plugin's method + if (typeof (zoomPlugin as any).zoomScale === 'function') { + (zoomPlugin as any).zoomScale('x', { + min: startWeek, + max: endWeek, + }); + } else if ((chart as any).zoomScale) { + (chart as any).zoomScale('x', { + min: startWeek, + max: endWeek, + }); + } + } + + // Also directly set scale options as fallback + if (xScale.options) { + (xScale.options as any).min = startWeek; + (xScale.options as any).max = endWeek; + } + + chart.update('none'); + } + + private updateOptions(): void { + const defaultOptions: ChartConfiguration['options'] = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: true, + position: 'top', + }, + }, + }; + + this.mergedOptions = { + ...defaultOptions, + ...this.chartOptions, + }; + } +} + diff --git a/src/app/shared/components/confetti/confetti.component.ts b/src/app/shared/components/confetti/confetti.component.ts new file mode 100644 index 0000000..4c895d2 --- /dev/null +++ b/src/app/shared/components/confetti/confetti.component.ts @@ -0,0 +1,128 @@ +import { Component, OnInit, OnDestroy, ElementRef, ViewChild, AfterViewInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +interface ConfettiParticle { + x: number; + y: number; + vx: number; + vy: number; + color: string; + size: number; + rotation: number; + rotationSpeed: number; +} + +@Component({ + selector: 'app-confetti', + standalone: true, + imports: [CommonModule], + template: ` + + `, + styles: [ + ` + canvas { + display: block; + } + `, + ], +}) +export class ConfettiComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('canvas', { static: false }) canvasRef!: ElementRef; + + private ctx!: CanvasRenderingContext2D; + private particles: ConfettiParticle[] = []; + private animationId: number | null = null; + private colors = ['#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b', '#10b981', '#ef4444']; + + ngOnInit(): void {} + + ngAfterViewInit(): void { + const canvas = this.canvasRef.nativeElement; + this.ctx = canvas.getContext('2d')!; + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + this.createParticles(); + this.animate(); + + window.addEventListener('resize', () => this.onResize()); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + window.removeEventListener('resize', () => this.onResize()); + } + + private createParticles(): void { + const count = 150; + for (let i = 0; i < count; i++) { + this.particles.push({ + x: Math.random() * window.innerWidth, + y: -10, + vx: (Math.random() - 0.5) * 4, + vy: Math.random() * 3 + 2, + color: this.colors[Math.floor(Math.random() * this.colors.length)], + size: Math.random() * 8 + 4, + rotation: Math.random() * Math.PI * 2, + rotationSpeed: (Math.random() - 0.5) * 0.2, + }); + } + } + + private animate = (): void => { + this.animationId = requestAnimationFrame(this.animate); + + this.ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + + for (let i = this.particles.length - 1; i >= 0; i--) { + const p = this.particles[i]; + + p.x += p.vx; + p.y += p.vy; + p.vy += 0.1; // Gravity + p.rotation += p.rotationSpeed; + + this.ctx.save(); + this.ctx.translate(p.x, p.y); + this.ctx.rotate(p.rotation); + this.ctx.fillStyle = p.color; + this.ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size); + this.ctx.restore(); + + if (p.y > window.innerHeight + 10) { + this.particles.splice(i, 1); + } + } + + if (this.particles.length === 0) { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + } + }; + + private onResize(): void { + const canvas = this.canvasRef.nativeElement; + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + } +} + + + + + + + + + + + + + + + + diff --git a/src/app/shared/components/custom-cursor/custom-cursor.component.ts b/src/app/shared/components/custom-cursor/custom-cursor.component.ts new file mode 100644 index 0000000..1d1a797 --- /dev/null +++ b/src/app/shared/components/custom-cursor/custom-cursor.component.ts @@ -0,0 +1,239 @@ +import { + Component, + OnInit, + OnDestroy, + HostListener, + signal, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-custom-cursor', + standalone: true, + imports: [CommonModule], + template: ` +
+
+ `, + styles: [ + ` + @keyframes pulse { + 0%, 100% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + 50% { + opacity: 0.7; + transform: translate(-50%, -50%) scale(1.1); + } + } + + @keyframes rotate { + from { + transform: translate(-50%, -50%) rotate(0deg); + } + to { + transform: translate(-50%, -50%) rotate(360deg); + } + } + + @keyframes glow { + 0%, 100% { + box-shadow: 0 0 5px rgba(255, 255, 255, 0.5), + 0 0 10px rgba(255, 255, 255, 0.3); + } + 50% { + box-shadow: 0 0 10px rgba(255, 255, 255, 0.8), + 0 0 20px rgba(255, 255, 255, 0.5), + 0 0 30px rgba(255, 255, 255, 0.3); + } + } + + .cursor-dot { + position: fixed; + pointer-events: none; + z-index: 9999; + width: 8px; + height: 8px; + background: white; + border-radius: 50%; + transform: translate(-50%, -50%); + mix-blend-mode: difference; + transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), + width 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), + height 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + } + + .cursor-dot.hover { + width: 12px; + height: 12px; + transform: translate(-50%, -50%) scale(1.5); + animation: pulse 1.5s ease-in-out infinite; + } + + .cursor-ring { + position: fixed; + pointer-events: none; + z-index: 9998; + width: 40px; + height: 40px; + border: 2px solid white; + border-radius: 50%; + transform: translate(-50%, -50%); + mix-blend-mode: difference; + transition: width 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), + height 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), + border-width 0.3s ease-out, border-color 0.3s ease-out, + opacity 0.3s ease-out; + } + + .cursor-ring.hover { + width: 25px; + height: 25px; + border-width: 3px; + border-color: rgba(255, 255, 255, 1); + opacity: 0.9; + animation: rotate 3s linear infinite, glow 2s ease-in-out infinite; + } + + @media (hover: none) { + .cursor-dot, + .cursor-ring { + display: none; + } + } + + /* Hide default cursor */ + :host { + cursor: none; + } + `, + ], +}) +export class CustomCursorComponent implements OnInit, OnDestroy { + x = 0; + y = 0; + ringX = 0; + ringY = 0; + private animationId: number | null = null; + private targetX = 0; + private targetY = 0; + readonly isHoveringClickable = signal(false); + private readonly clickableSelectors = [ + 'a', + 'button', + '[role="button"]', + 'input', + 'textarea', + 'select', + '[onclick]', + '[ng-click]', + '.clickable', + '[tabindex]:not([tabindex="-1"])', + ].join(','); + + ngOnInit(): void { + this.animate(); + this.setupClickableDetection(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + this.cleanupClickableDetection(); + } + + @HostListener('document:mousemove', ['$event']) + onMouseMove(event: MouseEvent): void { + this.x = event.clientX; + this.y = event.clientY; + this.targetX = event.clientX; + this.targetY = event.clientY; + this.checkClickableElement(event.target as Element); + } + + private readonly isMouseDown = signal(false); + readonly ringSize = signal(40); + + @HostListener('document:mousedown', []) + onMouseDown(): void { + this.isMouseDown.set(true); + this.updateRingSize(); + } + + @HostListener('document:mouseup', []) + onMouseUp(): void { + this.isMouseDown.set(false); + this.updateRingSize(); + } + + private updateRingSize(): void { + const hovering = this.isHoveringClickable(); + const mouseDown = this.isMouseDown(); + + if (mouseDown) { + this.ringSize.set(hovering ? 20 : 25); + } else { + this.ringSize.set(hovering ? 25 : 40); + } + } + + private checkClickableElement(element: Element | null): void { + if (!element) { + this.isHoveringClickable.set(false); + this.updateRingSize(); + return; + } + + const isClickable = + element.matches(this.clickableSelectors) || + element.closest(this.clickableSelectors) !== null; + + this.isHoveringClickable.set(isClickable); + this.updateRingSize(); + } + + private setupClickableDetection(): void { + document.addEventListener('mouseover', this.handleMouseOver, true); + document.addEventListener('mouseout', this.handleMouseOut, true); + } + + private cleanupClickableDetection(): void { + document.removeEventListener('mouseover', this.handleMouseOver, true); + document.removeEventListener('mouseout', this.handleMouseOut, true); + } + + private readonly handleMouseOver = (event: MouseEvent): void => { + this.checkClickableElement(event.target as Element); + }; + + private readonly handleMouseOut = (event: MouseEvent): void => { + const relatedTarget = event.relatedTarget as Element | null; + if (!relatedTarget || !relatedTarget.closest(this.clickableSelectors)) { + this.isHoveringClickable.set(false); + this.updateRingSize(); + } + }; + + private animate = (): void => { + this.animationId = requestAnimationFrame(this.animate); + + // Smooth ring follow using lerp + this.ringX += (this.targetX - this.ringX) * 0.15; + this.ringY += (this.targetY - this.ringY) * 0.15; + }; +} + diff --git a/src/app/shared/components/duck-shooting-game/duck-shooting-game.component.ts b/src/app/shared/components/duck-shooting-game/duck-shooting-game.component.ts new file mode 100644 index 0000000..efc7bb6 --- /dev/null +++ b/src/app/shared/components/duck-shooting-game/duck-shooting-game.component.ts @@ -0,0 +1,506 @@ +import { + Component, + OnInit, + OnDestroy, + ElementRef, + ViewChild, + AfterViewInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import * as THREE from 'three'; + +interface Duck { + mesh: THREE.Group; + velocity: THREE.Vector3; + alive: boolean; +} + +interface Bullet { + mesh: THREE.Mesh; + velocity: THREE.Vector3; + active: boolean; +} + +@Component({ + selector: 'app-duck-shooting-game', + standalone: true, + imports: [CommonModule], + template: ` +
+ +
+
+ Score: {{ score }} | Ducks: {{ ducksShot }} +
+
+ `, + styles: [ + ` + .duck-shooting-game-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100vw; + height: 100vh; + z-index: 50; + pointer-events: none; + touch-action: pan-y; + } + canvas { + display: block; + cursor: none !important; + background: transparent; + width: 100vw; + height: 100vh; + pointer-events: auto; + touch-action: pan-y pinch-zoom; + } + .duck-shooting-game-container > div { + pointer-events: auto; + } + .crosshair { + position: fixed; + width: 25px; + height: 25px; + pointer-events: none; + z-index: 100; + transform: translate(-50%, -50%); + border: 2px solid rgba(255, 255, 255, 0.9); + border-radius: 50%; + box-shadow: 0 0 8px rgba(255, 255, 255, 0.6); + } + .crosshair::before, + .crosshair::after { + content: ''; + position: absolute; + background: rgba(255, 255, 255, 0.9); + box-shadow: 0 0 3px rgba(255, 255, 255, 0.6); + } + .crosshair::before { + left: 50%; + top: -4px; + width: 2px; + height: 8px; + transform: translateX(-50%); + } + .crosshair::after { + left: 50%; + bottom: -4px; + width: 2px; + height: 8px; + transform: translateX(-50%); + } + `, + ], +}) +export class DuckShootingGameComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('canvas', { static: false }) canvasRef!: ElementRef; + + private scene!: THREE.Scene; + private camera!: THREE.PerspectiveCamera; + private renderer!: THREE.WebGLRenderer; + private animationId: number | null = null; + private ducks: Duck[] = []; + private bullets: Bullet[] = []; + private raycaster = new THREE.Raycaster(); + private mouse = new THREE.Vector2(); + private duckSpawnTimer = 0; + private duckSpawnInterval = 2000; // Spawn every 2 seconds + crosshairX = 0; + crosshairY = 0; + score = 0; + ducksShot = 0; + gameOver = false; + private gameTime = 0; + private gameDuration = 60000; // 60 seconds + + ngOnInit(): void {} + + ngAfterViewInit(): void { + this.initThree(); + this.createScene(); + this.setupEventListeners(); + this.animate(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + if (this.renderer) { + this.renderer.dispose(); + } + } + + private initThree(): void { + const canvas = this.canvasRef.nativeElement; + const width = window.innerWidth; + const height = window.innerHeight; + + this.scene = new THREE.Scene(); + this.scene.background = null; + + this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); + this.camera.position.set(0, 5, 20); + this.camera.lookAt(0, 0, 0); + + this.renderer = new THREE.WebGLRenderer({ + canvas, + alpha: true, + antialias: true, + }); + this.renderer.setSize(width, height); + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + this.renderer.shadowMap.enabled = true; + + window.addEventListener('resize', () => this.onWindowResize()); + } + + private createScene(): void { + // Lighting + const ambientLight = new THREE.AmbientLight(0xffffff, 0.8); + this.scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 0.9); + directionalLight.position.set(10, 10, 5); + directionalLight.castShadow = true; + this.scene.add(directionalLight); + } + + private createDuck(): Duck { + const duck = new THREE.Group(); + + // Body (more duck-like shape - wider and flatter) + const bodyGeometry = new THREE.SphereGeometry(0.4, 16, 16); + bodyGeometry.scale(1.2, 0.8, 1.5); + const bodyMaterial = new THREE.MeshStandardMaterial({ + color: 0xffa500, + roughness: 0.7, + }); + const body = new THREE.Mesh(bodyGeometry, bodyMaterial); + body.castShadow = true; + duck.add(body); + + // Head (larger, more prominent) + const headGeometry = new THREE.SphereGeometry(0.3, 16, 16); + const headMaterial = new THREE.MeshStandardMaterial({ + color: 0xffa500, + roughness: 0.7, + }); + const head = new THREE.Mesh(headGeometry, headMaterial); + head.position.set(0, 0.4, 0.5); + head.castShadow = true; + duck.add(head); + + // Beak (more prominent, duck-like) + const beakGeometry = new THREE.ConeGeometry(0.12, 0.25, 8); + beakGeometry.scale(1, 1, 1.3); + const beakMaterial = new THREE.MeshStandardMaterial({ + color: 0xff8c00, + roughness: 0.5, + }); + const beak = new THREE.Mesh(beakGeometry, beakMaterial); + beak.rotation.x = Math.PI / 2; + beak.position.set(0, 0.4, 0.75); + duck.add(beak); + + // Left Wing + const wingGeometry = new THREE.SphereGeometry(0.25, 16, 16); + wingGeometry.scale(1.8, 0.6, 0.4); + const wingMaterial = new THREE.MeshStandardMaterial({ + color: 0xff8c00, + roughness: 0.7, + }); + const leftWing = new THREE.Mesh(wingGeometry, wingMaterial); + leftWing.position.set(0.35, 0, 0); + leftWing.castShadow = true; + duck.add(leftWing); + + // Right Wing + const rightWing = new THREE.Mesh(wingGeometry, wingMaterial); + rightWing.position.set(-0.35, 0, 0); + rightWing.castShadow = true; + duck.add(rightWing); + + // Left Eye + const eyeGeometry = new THREE.SphereGeometry(0.06, 8, 8); + const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 }); + const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial); + leftEye.position.set(0.12, 0.45, 0.6); + duck.add(leftEye); + + // Right Eye + const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial); + rightEye.position.set(-0.12, 0.45, 0.6); + duck.add(rightEye); + + // Tail (small) + const tailGeometry = new THREE.SphereGeometry(0.15, 12, 12); + tailGeometry.scale(0.8, 1.2, 0.6); + const tailMaterial = new THREE.MeshStandardMaterial({ + color: 0xff8c00, + roughness: 0.7, + }); + const tail = new THREE.Mesh(tailGeometry, tailMaterial); + tail.position.set(0, 0, -0.5); + duck.add(tail); + + // Random starting position (off screen to the left or right) + const side = Math.random() > 0.5 ? 1 : -1; + duck.position.set(side * 30, Math.random() * 10 + 2, Math.random() * 10 - 5); + + // Random velocity + const speed = 0.1 + Math.random() * 0.1; + const velocity = new THREE.Vector3(-side * speed, (Math.random() - 0.5) * 0.05, (Math.random() - 0.5) * 0.03); + + // Rotate duck to face direction of travel (90 degrees offset for proper orientation) + const angle = Math.atan2(velocity.x, velocity.z); + duck.rotation.y = angle + Math.PI / 2; + + this.scene.add(duck); + + return { + mesh: duck, + velocity, + alive: true, + }; + } + + private createBullet(position: THREE.Vector3, direction: THREE.Vector3): Bullet { + const bulletGeometry = new THREE.SphereGeometry(0.1, 8, 8); + const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 }); + const bulletMesh = new THREE.Mesh(bulletGeometry, bulletMaterial); + bulletMesh.position.copy(position); + + const speed = 1.5; + const velocity = direction.normalize().multiplyScalar(speed); + + this.scene.add(bulletMesh); + + return { + mesh: bulletMesh, + velocity, + active: true, + }; + } + + private setupEventListeners(): void { + const canvas = this.canvasRef.nativeElement; + + canvas.addEventListener('mousemove', (e) => { + this.crosshairX = e.clientX; + this.crosshairY = e.clientY; + const rect = canvas.getBoundingClientRect(); + this.mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; + this.mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; + }, { passive: true }); + + canvas.addEventListener('click', (e) => { + e.stopPropagation(); + this.shoot(); + }); + + // Allow wheel events to pass through for scrolling + canvas.addEventListener('wheel', (e) => { + // Don't prevent default - allow scrolling + }, { passive: true }); + + // Hide default cursor on the entire container + document.addEventListener('mousemove', () => { + if (this.canvasRef?.nativeElement) { + this.canvasRef.nativeElement.style.cursor = 'none'; + } + }, { passive: true }); + } + + private shoot(): void { + if (this.gameOver) return; + + this.raycaster.setFromCamera(this.mouse, this.camera); + const direction = this.raycaster.ray.direction.clone(); + + const bullet = this.createBullet(this.camera.position.clone(), direction); + this.bullets.push(bullet); + } + + private spawnDuck(): void { + if (this.ducks.length < 10) { + this.ducks.push(this.createDuck()); + } + } + + private updateDucks(): void { + for (let i = this.ducks.length - 1; i >= 0; i--) { + const duck = this.ducks[i]; + if (!duck.alive) continue; + + duck.mesh.position.add(duck.velocity); + + // Rotate duck to face direction of travel + const angle = Math.atan2(duck.velocity.x, duck.velocity.z); + duck.mesh.rotation.y = angle + Math.PI / 2; + + // Animate wing flapping (both wings) + const leftWing = duck.mesh.children.find((child) => child.position.x > 0.3); + const rightWing = duck.mesh.children.find((child) => child.position.x < -0.3); + const flapAngle = Math.sin(Date.now() * 0.015 + i) * 0.4; + if (leftWing) { + leftWing.rotation.z = flapAngle; + } + if (rightWing) { + rightWing.rotation.z = -flapAngle; + } + + // Remove if off screen + if (Math.abs(duck.mesh.position.x) > 40 || duck.mesh.position.y < -5 || duck.mesh.position.y > 15) { + this.scene.remove(duck.mesh); + duck.mesh.children.forEach((child) => { + if (child instanceof THREE.Mesh) { + child.geometry.dispose(); + if (Array.isArray(child.material)) { + child.material.forEach((m) => m.dispose()); + } else { + child.material.dispose(); + } + } + }); + this.ducks.splice(i, 1); + } + } + } + + private updateBullets(): void { + for (let i = this.bullets.length - 1; i >= 0; i--) { + const bullet = this.bullets[i]; + if (!bullet.active) continue; + + bullet.mesh.position.add(bullet.velocity); + + // Remove if too far + if (bullet.mesh.position.distanceTo(this.camera.position) > 100) { + this.scene.remove(bullet.mesh); + bullet.mesh.geometry.dispose(); + (bullet.mesh.material as THREE.Material).dispose(); + this.bullets.splice(i, 1); + continue; + } + + // Check collision with ducks + for (const duck of this.ducks) { + if (!duck.alive) continue; + + const distance = bullet.mesh.position.distanceTo(duck.mesh.position); + if (distance < 0.8) { + // Hit! + duck.alive = false; + bullet.active = false; + + // Animate duck falling + duck.velocity.set(0, -0.2, 0); + duck.mesh.rotation.x = Math.PI / 2; + + this.score += 10; + this.ducksShot++; + + // Remove bullet + this.scene.remove(bullet.mesh); + bullet.mesh.geometry.dispose(); + (bullet.mesh.material as THREE.Material).dispose(); + this.bullets.splice(i, 1); + + // Remove duck after a delay + setTimeout(() => { + this.scene.remove(duck.mesh); + duck.mesh.children.forEach((child) => { + if (child instanceof THREE.Mesh) { + child.geometry.dispose(); + if (Array.isArray(child.material)) { + child.material.forEach((m) => m.dispose()); + } else { + child.material.dispose(); + } + } + }); + const index = this.ducks.indexOf(duck); + if (index > -1) { + this.ducks.splice(index, 1); + } + }, 1000); + + break; + } + } + } + } + + private animate = (): void => { + this.animationId = requestAnimationFrame(this.animate); + + if (!this.gameOver) { + this.gameTime += 16; // ~60fps + + // Spawn ducks + this.duckSpawnTimer += 16; + if (this.duckSpawnTimer >= this.duckSpawnInterval) { + this.spawnDuck(); + this.duckSpawnTimer = 0; + // Decrease spawn interval over time + this.duckSpawnInterval = Math.max(1000, this.duckSpawnInterval - 10); + } + + // Auto restart after game duration + if (this.gameTime >= this.gameDuration) { + this.restart(); + } + + this.updateDucks(); + this.updateBullets(); + } + + this.renderer.render(this.scene, this.camera); + }; + + private onWindowResize(): void { + const width = window.innerWidth; + const height = window.innerHeight; + + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(width, height); + } + + restart(): void { + // Clean up existing ducks and bullets + this.ducks.forEach((duck) => { + this.scene.remove(duck.mesh); + duck.mesh.children.forEach((child) => { + if (child instanceof THREE.Mesh) { + child.geometry.dispose(); + if (Array.isArray(child.material)) { + child.material.forEach((m) => m.dispose()); + } else { + child.material.dispose(); + } + } + }); + }); + this.ducks = []; + + this.bullets.forEach((bullet) => { + this.scene.remove(bullet.mesh); + bullet.mesh.geometry.dispose(); + (bullet.mesh.material as THREE.Material).dispose(); + }); + this.bullets = []; + + this.score = 0; + this.ducksShot = 0; + this.gameOver = false; + this.gameTime = 0; + this.duckSpawnTimer = 0; + this.duckSpawnInterval = 2000; + } +} + diff --git a/src/app/shared/components/footer/footer.component.ts b/src/app/shared/components/footer/footer.component.ts new file mode 100644 index 0000000..c033531 --- /dev/null +++ b/src/app/shared/components/footer/footer.component.ts @@ -0,0 +1,114 @@ +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterLink } from '@angular/router'; + +@Component({ + selector: 'app-footer', + standalone: true, + imports: [CommonModule, RouterLink], + template: ` +
+
+
+ +
+
+ 🎯 +

2026 Goals Tracker

+
+

+ Track your sport and gaming goals for 2026. Stay motivated and achieve your targets! +

+
+ + + + + +
+

About

+
    +
  • + 📅 + Tracking goals for 2026 +
  • +
  • + + Built with Angular 19 +
  • +
  • + 🔗 + Integrated with Strava, Riot Games, Faceit +
  • +
+
+
+ + +
+
+

+ © {{ currentYear }} 2026 Goals Tracker. All rights reserved. +

+
+ + + Stay motivated! + +
+
+
+
+
+ `, + styles: [], +}) +export class FooterComponent { + readonly currentYear = new Date().getFullYear(); +} + + + + + + + + + + + + + + + diff --git a/src/app/shared/components/loading-screen/loading-screen.component.ts b/src/app/shared/components/loading-screen/loading-screen.component.ts new file mode 100644 index 0000000..152e23e --- /dev/null +++ b/src/app/shared/components/loading-screen/loading-screen.component.ts @@ -0,0 +1,229 @@ +import { + Component, + OnInit, + OnDestroy, + ElementRef, + ViewChild, + AfterViewInit, + Input, + signal, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import * as THREE from 'three'; + +@Component({ + selector: 'app-loading-screen', + standalone: true, + imports: [CommonModule], + template: ` +
+ +
+

+ 🎯 + 2026 Goals Tracker +

+
+
+
+
+
+

{{ getLoadingText() }}

+
+
+ `, + styles: [ + ` + canvas { + display: block; + width: 100%; + height: 100%; + } + @keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } + } + .animate-shimmer { + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.4) 50%, + transparent 100% + ); + background-size: 200% 100%; + animation: shimmer 2s infinite; + } + `, + ], +}) +export class LoadingScreenComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('canvas', { static: false }) canvasRef!: ElementRef; + @Input() progress = signal(0); + @Input() loadingText = signal('Loading...'); + @Input() fadeOut = signal(false); + + private scene!: THREE.Scene; + private camera!: THREE.PerspectiveCamera; + private renderer!: THREE.WebGLRenderer; + private particles!: THREE.Points; + private animationId: number | null = null; + private particleCount = 300; + private particleVelocities: Float32Array | null = null; + + ngOnInit(): void {} + + getProgress(): number { + return typeof this.progress === 'function' ? this.progress() : this.progress; + } + + getLoadingText(): string { + return typeof this.loadingText === 'function' ? this.loadingText() : this.loadingText; + } + + getFadeOut(): boolean { + return typeof this.fadeOut === 'function' ? this.fadeOut() : this.fadeOut; + } + + ngAfterViewInit(): void { + this.initThree(); + this.createParticles(); + this.animate(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + if (this.renderer) { + this.renderer.dispose(); + } + if (this.particles) { + this.particles.geometry.dispose(); + (this.particles.material as THREE.PointsMaterial).dispose(); + } + } + + private initThree(): void { + const canvas = this.canvasRef.nativeElement; + const width = window.innerWidth; + const height = window.innerHeight; + + // Scene + this.scene = new THREE.Scene(); + + // Camera + this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); + this.camera.position.z = 5; + + // Renderer + this.renderer = new THREE.WebGLRenderer({ + canvas, + alpha: true, + antialias: true, + }); + this.renderer.setSize(width, height); + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + this.renderer.setClearColor(0x000000, 0); + + // Handle resize + window.addEventListener('resize', () => this.onWindowResize()); + } + + private createParticles(): void { + const geometry = new THREE.BufferGeometry(); + const positions = new Float32Array(this.particleCount * 3); + const colors = new Float32Array(this.particleCount * 3); + this.particleVelocities = new Float32Array(this.particleCount * 3); + + const colorPalette = [ + new THREE.Color(0x3b82f6), // Blue + new THREE.Color(0x8b5cf6), // Purple + new THREE.Color(0xec4899), // Pink + ]; + + for (let i = 0; i < this.particleCount; i++) { + const i3 = i * 3; + + // Position + positions[i3] = (Math.random() - 0.5) * 10; + positions[i3 + 1] = (Math.random() - 0.5) * 10; + positions[i3 + 2] = (Math.random() - 0.5) * 10; + + // Color + const color = colorPalette[Math.floor(Math.random() * colorPalette.length)]; + colors[i3] = color.r; + colors[i3 + 1] = color.g; + colors[i3 + 2] = color.b; + + // Velocity + this.particleVelocities![i3] = (Math.random() - 0.5) * 0.02; + this.particleVelocities![i3 + 1] = (Math.random() - 0.5) * 0.02; + this.particleVelocities![i3 + 2] = (Math.random() - 0.5) * 0.02; + } + + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); + + const material = new THREE.PointsMaterial({ + size: 0.1, + vertexColors: true, + transparent: true, + opacity: 0.8, + blending: THREE.AdditiveBlending, + }); + + this.particles = new THREE.Points(geometry, material); + this.scene.add(this.particles); + } + + private animate = (): void => { + this.animationId = requestAnimationFrame(this.animate); + + if (!this.particles || !this.particleVelocities) return; + + const positions = this.particles.geometry.attributes['position'].array as Float32Array; + const velocities = this.particleVelocities; + + // Update particle positions + for (let i = 0; i < positions.length; i += 3) { + positions[i] += velocities[i]; + positions[i + 1] += velocities[i + 1]; + positions[i + 2] += velocities[i + 2]; + + // Wrap around boundaries + if (Math.abs(positions[i]) > 5) velocities[i] *= -1; + if (Math.abs(positions[i + 1]) > 5) velocities[i + 1] *= -1; + if (Math.abs(positions[i + 2]) > 5) velocities[i + 2] *= -1; + } + + // Rotate particles around center + this.particles.rotation.y += 0.001; + this.particles.rotation.x += 0.0005; + + // Update geometry + this.particles.geometry.attributes['position'].needsUpdate = true; + + this.renderer.render(this.scene, this.camera); + }; + + private onWindowResize(): void { + const width = window.innerWidth; + const height = window.innerHeight; + + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(width, height); + } +} + diff --git a/src/app/shared/components/magic-ball-game/magic-ball-game.component.ts b/src/app/shared/components/magic-ball-game/magic-ball-game.component.ts new file mode 100644 index 0000000..839b0fe --- /dev/null +++ b/src/app/shared/components/magic-ball-game/magic-ball-game.component.ts @@ -0,0 +1,327 @@ +import { + Component, + OnInit, + OnDestroy, + ElementRef, + ViewChild, + AfterViewInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import * as THREE from 'three'; + +interface Block { + mesh: THREE.Mesh; + alive: boolean; +} + +@Component({ + selector: 'app-magic-ball-game', + standalone: true, + imports: [CommonModule], + template: ` +
+ +
+ Score: {{ score }} | Lives: {{ lives }} +
+
+ Move mouse up/down to control paddle +
+ @if (gameOver) { +
+
+

Game Over!

+

Final Score: {{ score }}

+ +
+
+ } +
+ `, + styles: [ + ` + .magic-ball-game-container { + position: fixed; + inset: 0; + z-index: 50; + background: rgba(0, 0, 0, 0.3); + backdrop-filter: blur(2px); + } + canvas { + display: block; + } + `, + ], +}) +export class MagicBallGameComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('canvas', { static: false }) canvasRef!: ElementRef; + + private scene!: THREE.Scene; + private camera!: THREE.OrthographicCamera; + private renderer!: THREE.WebGLRenderer; + private animationId: number | null = null; + private ball!: THREE.Mesh; + private paddle!: THREE.Mesh; + private blocks: Block[] = []; + private ballVelocity = new THREE.Vector2(0.15, 0.15); + private mouseY = 0; + private aspect = 1; + score = 0; + lives = 3; + gameOver = false; + + ngOnInit(): void {} + + ngAfterViewInit(): void { + this.initThree(); + this.createScene(); + this.setupEventListeners(); + this.animate(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + if (this.renderer) { + this.renderer.dispose(); + } + } + + private initThree(): void { + const canvas = this.canvasRef.nativeElement; + const width = window.innerWidth; + const height = window.innerHeight; + this.aspect = width / height; + + this.scene = new THREE.Scene(); + this.scene.background = null; + + // Orthographic camera for 2D + const viewSize = 20; + this.camera = new THREE.OrthographicCamera( + -viewSize * this.aspect, + viewSize * this.aspect, + viewSize, + -viewSize, + 0.1, + 1000 + ); + this.camera.position.z = 10; + + this.renderer = new THREE.WebGLRenderer({ + canvas, + alpha: true, + antialias: true, + }); + this.renderer.setSize(width, height); + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + + window.addEventListener('resize', () => this.onWindowResize()); + } + + private createScene(): void { + // Ball (2D circle) + const ballGeometry = new THREE.CircleGeometry(0.4, 32); + const ballMaterial = new THREE.MeshBasicMaterial({ + color: 0x8b5cf6, + }); + this.ball = new THREE.Mesh(ballGeometry, ballMaterial); + this.ball.position.set(-15, 0, 0); + this.scene.add(this.ball); + + // Paddle (on the left, vertical rectangle) + const paddleGeometry = new THREE.PlaneGeometry(0.6, 4); + const paddleMaterial = new THREE.MeshBasicMaterial({ + color: 0xec4899, + }); + this.paddle = new THREE.Mesh(paddleGeometry, paddleMaterial); + this.paddle.position.set(-17, 0, 0); + this.scene.add(this.paddle); + + // Create blocks (on the right side, 2D) + this.createBlocks(); + } + + private createBlocks(): void { + const rows = 8; + const cols = 4; + const blockWidth = 2; + const blockHeight = 1.5; + const spacing = 0.3; + const startX = 12; + const startY = (rows * (blockHeight + spacing)) / 2 - blockHeight / 2; + + const colors = [0xff0000, 0xff8800, 0xffff00, 0x00ff00, 0x0088ff, 0x0000ff, 0x8800ff, 0xff00ff]; + + for (let row = 0; row < rows; row++) { + for (let col = 0; col < cols; col++) { + const blockGeometry = new THREE.PlaneGeometry(blockWidth, blockHeight); + const blockMaterial = new THREE.MeshBasicMaterial({ + color: colors[row % colors.length], + }); + const blockMesh = new THREE.Mesh(blockGeometry, blockMaterial); + blockMesh.position.set( + startX + col * (blockWidth + spacing), + startY - row * (blockHeight + spacing), + 0 + ); + this.scene.add(blockMesh); + this.blocks.push({ mesh: blockMesh, alive: true }); + } + } + } + + private setupEventListeners(): void { + const canvas = this.canvasRef.nativeElement; + + canvas.addEventListener('mousemove', (e) => { + const rect = canvas.getBoundingClientRect(); + const normalizedY = ((e.clientY - rect.top) / rect.height) * 2 - 1; + const viewSize = 20; + this.mouseY = -normalizedY * viewSize; // Convert to world space + }); + } + + private updatePaddle(): void { + this.paddle.position.y = this.mouseY; + // Keep paddle in bounds + const viewSize = 20; + this.paddle.position.y = Math.max(-viewSize + 2, Math.min(viewSize - 2, this.paddle.position.y)); + } + + private updateBall(): void { + if (this.gameOver) return; + + this.ball.position.x += this.ballVelocity.x; + this.ball.position.y += this.ballVelocity.y; + + const viewSize = 20; + + // Bounce off top/bottom walls + if (Math.abs(this.ball.position.y) > viewSize - 0.5) { + this.ballVelocity.y *= -1; + this.ball.position.y = Math.max(-viewSize + 0.5, Math.min(viewSize - 0.5, this.ball.position.y)); + } + + // Bounce off right wall + if (this.ball.position.x > viewSize * this.aspect - 0.5) { + this.ballVelocity.x *= -1; + this.ball.position.x = viewSize * this.aspect - 0.5; + } + + // Paddle collision (left side) + const paddleBox = new THREE.Box2( + new THREE.Vector2(this.paddle.position.x - 0.3, this.paddle.position.y - 2), + new THREE.Vector2(this.paddle.position.x + 0.3, this.paddle.position.y + 2) + ); + const ballPos = new THREE.Vector2(this.ball.position.x, this.ball.position.y); + const ballRadius = 0.4; + + if ( + ballPos.x - ballRadius < paddleBox.max.x && + ballPos.x + ballRadius > paddleBox.min.x && + ballPos.y - ballRadius < paddleBox.max.y && + ballPos.y + ballRadius > paddleBox.min.y && + this.ballVelocity.x < 0 + ) { + this.ballVelocity.x *= -1; + // Add some angle based on where ball hits paddle + const hitY = (this.ball.position.y - this.paddle.position.y) / 2; + this.ballVelocity.y += hitY * 0.05; + this.ball.position.x = -16.5; + } + + // Block collisions + for (const block of this.blocks) { + if (!block.alive) continue; + + const blockBox = new THREE.Box2( + new THREE.Vector2(block.mesh.position.x - 1, block.mesh.position.y - 0.75), + new THREE.Vector2(block.mesh.position.x + 1, block.mesh.position.y + 0.75) + ); + + if ( + ballPos.x - ballRadius < blockBox.max.x && + ballPos.x + ballRadius > blockBox.min.x && + ballPos.y - ballRadius < blockBox.max.y && + ballPos.y + ballRadius > blockBox.min.y + ) { + block.alive = false; + block.mesh.visible = false; + this.score += 10; + + // Determine bounce direction + const blockCenter = new THREE.Vector2(block.mesh.position.x, block.mesh.position.y); + const dx = ballPos.x - blockCenter.x; + const dy = ballPos.y - blockCenter.y; + + if (Math.abs(dx) > Math.abs(dy)) { + this.ballVelocity.x *= -1; + } else { + this.ballVelocity.y *= -1; + } + + break; + } + } + + // Check if ball went past paddle (left side) + if (this.ball.position.x < -viewSize * this.aspect) { + this.lives--; + if (this.lives <= 0) { + this.gameOver = true; + } else { + // Reset ball + this.ball.position.set(-15, 0, 0); + this.ballVelocity.set(0.15, 0.15); + } + } + + // Check if all blocks destroyed + if (this.blocks.every((b) => !b.alive)) { + // Reset blocks + this.blocks.forEach((block) => { + block.alive = true; + block.mesh.visible = true; + }); + this.ballVelocity.multiplyScalar(1.1); // Speed up + } + } + + private animate = (): void => { + this.animationId = requestAnimationFrame(this.animate); + this.updatePaddle(); + this.updateBall(); + this.renderer.render(this.scene, this.camera); + }; + + private onWindowResize(): void { + const width = window.innerWidth; + const height = window.innerHeight; + this.aspect = width / height; + + const viewSize = 20; + this.camera.left = -viewSize * this.aspect; + this.camera.right = viewSize * this.aspect; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(width, height); + } + + restart(): void { + this.score = 0; + this.lives = 3; + this.gameOver = false; + this.ball.position.set(-15, 0, 0); + this.ballVelocity.set(0.15, 0.15); + this.blocks.forEach((block) => { + block.alive = true; + block.mesh.visible = true; + }); + } +} diff --git a/src/app/shared/components/match-history/match-history.component.ts b/src/app/shared/components/match-history/match-history.component.ts new file mode 100644 index 0000000..339ae48 --- /dev/null +++ b/src/app/shared/components/match-history/match-history.component.ts @@ -0,0 +1,63 @@ +import { Component, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Match } from '../../../core/models/gaming-goal.model'; +import { formatDate } from '../../utils/date.utils'; + +@Component({ + selector: 'app-match-history', + standalone: true, + imports: [CommonModule], + template: ` +
+

+ Recent Matches +

+
+
+
+ +
+

+ {{ formatDate(match.date) }} +

+

+ {{ match.rank }} +

+
+
+
+ + {{ match.result.toUpperCase() }} + +

+ Score: {{ match.score }} +

+
+
+

+ No recent matches available +

+
+
+ `, + styles: [], +}) +export class MatchHistoryComponent { + @Input() matches: Match[] = []; + + readonly formatDate = formatDate; +} + diff --git a/src/app/shared/components/navbar/navbar.component.ts b/src/app/shared/components/navbar/navbar.component.ts new file mode 100644 index 0000000..73339b1 --- /dev/null +++ b/src/app/shared/components/navbar/navbar.component.ts @@ -0,0 +1,174 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterLink, RouterLinkActive, Router } from '@angular/router'; + +@Component({ + selector: 'app-navbar', + standalone: true, + imports: [CommonModule, RouterLink, RouterLinkActive], + template: ` + + `, + styles: [ + ` + :host { + display: block; + } + a[routerLinkActive] { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + `, + ], +}) +export class NavbarComponent implements OnInit { + mobileMenuOpen = false; + + constructor(private readonly router: Router) {} + + ngOnInit(): void { + // Close mobile menu on route change + this.router.events.subscribe(() => { + this.closeMobileMenu(); + }); + } + + toggleMobileMenu(): void { + this.mobileMenuOpen = !this.mobileMenuOpen; + } + + closeMobileMenu(): void { + this.mobileMenuOpen = false; + } +} + + + + + + + + + + + + + + + diff --git a/src/app/shared/components/particle-background/particle-background.component.ts b/src/app/shared/components/particle-background/particle-background.component.ts new file mode 100644 index 0000000..e68cc09 --- /dev/null +++ b/src/app/shared/components/particle-background/particle-background.component.ts @@ -0,0 +1,468 @@ +import { + Component, + OnInit, + OnDestroy, + ElementRef, + ViewChild, + AfterViewInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import * as THREE from 'three'; + +@Component({ + selector: 'app-particle-background', + standalone: true, + imports: [CommonModule], + template: ` + + `, + styles: [ + ` + canvas { + display: block; + z-index: 0; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + } + `, + ], +}) +export class ParticleBackgroundComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('canvas', { static: false }) canvasRef!: ElementRef; + + private scene!: THREE.Scene; + private camera!: THREE.PerspectiveCamera; + private renderer!: THREE.WebGLRenderer; + private particles!: THREE.Points; + private animationId: number | null = null; + private particleCount = 500; + private mouseX = 0; + private mouseY = 0; + private mouseWorldX = 0; + private mouseWorldY = 0; + private particleVelocities: Float32Array | null = null; + private mouseDownTime = 0; + private isMouseDown = false; + private clickWorldX = 0; + private clickWorldY = 0; + private repulsionWaves: Array<{ + x: number; + y: number; + radius: number; + maxRadius: number; + opacity: number; + time: number; + }> = []; + private waveObjects: THREE.Mesh[] = []; + + ngOnInit(): void {} + + ngAfterViewInit(): void { + this.initThree(); + this.createParticles(); + this.animate(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + // Clean up wave objects + this.waveObjects.forEach((wave) => { + this.scene.remove(wave); + wave.geometry.dispose(); + (wave.material as THREE.Material).dispose(); + }); + this.waveObjects = []; + if (this.renderer) { + this.renderer.dispose(); + } + } + + private initThree(): void { + const canvas = this.canvasRef.nativeElement; + const width = window.innerWidth; + const height = window.innerHeight; + + // Scene + this.scene = new THREE.Scene(); + + // Camera + this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); + this.camera.position.z = 5; + + // Renderer + this.renderer = new THREE.WebGLRenderer({ + canvas: canvas, + alpha: true, + antialias: true, + }); + this.renderer.setSize(width, height); + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + + window.addEventListener('resize', () => this.onWindowResize()); + this.addMouseInteraction(); + } + + private screenToWorld(x: number, y: number): { x: number; y: number } { + // Convert screen coordinates to normalized device coordinates + const mouseX = (x / window.innerWidth) * 2 - 1; + const mouseY = -(y / window.innerHeight) * 2 + 1; + + // Create a vector in normalized device coordinates + const vector = new THREE.Vector3(mouseX, mouseY, 0.5); + + // Unproject to world coordinates + vector.unproject(this.camera); + + // Calculate direction from camera + const dir = vector.sub(this.camera.position).normalize(); + + // Find intersection with z=0 plane (where particles are) + const distance = -this.camera.position.z / dir.z; + const pos = this.camera.position.clone().add(dir.multiplyScalar(distance)); + + return { x: pos.x, y: pos.y }; + } + + private addMouseInteraction(): void { + let lastMouseEvent: MouseEvent | null = null; + + window.addEventListener('mousemove', (event) => { + lastMouseEvent = event; + this.mouseX = (event.clientX / window.innerWidth) * 2 - 1; + this.mouseY = -(event.clientY / window.innerHeight) * 2 + 1; + const worldPos = this.screenToWorld(event.clientX, event.clientY); + this.mouseWorldX = worldPos.x; + this.mouseWorldY = worldPos.y; + }); + + window.addEventListener('mousedown', (event) => { + this.isMouseDown = true; + this.mouseDownTime = Date.now(); + }); + + window.addEventListener('mouseup', (event) => { + if (this.isMouseDown) { + // Calculate world position from event coordinates directly + const worldPos = this.screenToWorld(event.clientX, event.clientY); + this.clickWorldX = worldPos.x; + this.clickWorldY = worldPos.y; + const holdDuration = Date.now() - this.mouseDownTime; + this.applyRepulsion(holdDuration); + this.isMouseDown = false; + } + }); + + // Handle mouse leaving window + window.addEventListener('mouseleave', () => { + if (this.isMouseDown && lastMouseEvent) { + // Use last known mouse position with proper conversion + const worldPos = this.screenToWorld(lastMouseEvent.clientX, lastMouseEvent.clientY); + this.clickWorldX = worldPos.x; + this.clickWorldY = worldPos.y; + const holdDuration = Date.now() - this.mouseDownTime; + this.applyRepulsion(holdDuration); + this.isMouseDown = false; + } + }); + } + + private applyRepulsion(holdDuration: number): void { + if (!this.particleVelocities) return; + + const positions = this.particles.geometry.attributes['position'].array as Float32Array; + const velocities = this.particleVelocities; + + // Calculate power based on hold duration with limits + const minHoldTime = 50; // Minimum 50ms to register + const maxHoldTime = 1000; // Maximum 1 second for full power + const clampedHold = Math.max(minHoldTime, Math.min(holdDuration, maxHoldTime)); + const normalizedHold = (clampedHold - minHoldTime) / (maxHoldTime - minHoldTime); + + // Strength limits + const baseStrength = 0.15; + const maxStrength = 0.8; + const repulsionStrength = Math.min( + baseStrength + (maxStrength - baseStrength) * normalizedHold, + maxStrength + ); + + // Radius limits + const minRadius = 2.5; + const maxRadius = 5; + const repulsionRadius = Math.min( + minRadius + (maxRadius - minRadius) * normalizedHold, + maxRadius + ); + + // Add visual repulsion wave - scale based on hold duration + const waveOpacity = 0.3 + normalizedHold * 0.3; // 0.3 to 0.6 based on hold + const waveMaxRadius = repulsionRadius * (1.2 + normalizedHold * 0.5); // 1.2x to 1.7x based on hold + + this.repulsionWaves.push({ + x: this.clickWorldX, + y: this.clickWorldY, + radius: 0, + maxRadius: waveMaxRadius, + opacity: waveOpacity, + time: Date.now(), + }); + + for (let i = 0; i < positions.length; i += 3) { + const x = positions[i]; + const y = positions[i + 1]; + const z = positions[i + 2]; + + // Calculate distance from click point + const dx = x - this.clickWorldX; + const dy = y - this.clickWorldY; + const distance = Math.sqrt(dx * dx + dy * dy); + + // Apply repulsion if within radius + if (distance < repulsionRadius && distance > 0.1) { + const normalizedDx = dx / distance; + const normalizedDy = dy / distance; + const force = (1 - distance / repulsionRadius) * repulsionStrength; + + // Add force to velocity (physics-based) + velocities[i] += normalizedDx * force; + velocities[i + 1] += normalizedDy * force; + velocities[i + 2] += (Math.random() - 0.5) * force * 0.4; // Z-axis movement + } + } + } + + private createParticles(): void { + const geometry = new THREE.BufferGeometry(); + const positions = new Float32Array(this.particleCount * 3); + const colors = new Float32Array(this.particleCount * 3); + + const color1 = new THREE.Color(0x3b82f6); // Blue + const color2 = new THREE.Color(0x8b5cf6); // Purple + const color3 = new THREE.Color(0xec4899); // Pink + + for (let i = 0; i < this.particleCount; i++) { + const i3 = i * 3; + + // Position + positions[i3] = (Math.random() - 0.5) * 20; + positions[i3 + 1] = (Math.random() - 0.5) * 20; + positions[i3 + 2] = (Math.random() - 0.5) * 20; + + // Color + const colorChoice = Math.random(); + let color: THREE.Color; + if (colorChoice < 0.33) { + color = color1; + } else if (colorChoice < 0.66) { + color = color2; + } else { + color = color3; + } + + colors[i3] = color.r; + colors[i3 + 1] = color.g; + colors[i3 + 2] = color.b; + } + + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); + + // Initialize velocities for physics + this.particleVelocities = new Float32Array(this.particleCount * 3); + for (let i = 0; i < this.particleVelocities.length; i++) { + this.particleVelocities[i] = 0; + } + + const material = new THREE.PointsMaterial({ + size: 0.08, + vertexColors: true, + transparent: true, + opacity: 0.3, + blending: THREE.AdditiveBlending, + }); + + this.particles = new THREE.Points(geometry, material); + this.scene.add(this.particles); + } + + private animate = (): void => { + this.animationId = requestAnimationFrame(this.animate); + + if (this.particles) { + // Much slower rotation + this.particles.rotation.x += 0.0001; + this.particles.rotation.y += 0.0002; + + // Mouse interaction - subtle rotation + this.particles.rotation.x += this.mouseY * 0.0001; + this.particles.rotation.y += this.mouseX * 0.00015; + + // Physics-based movement with ease-out + const positions = this.particles.geometry.attributes['position'].array as Float32Array; + const velocities = this.particleVelocities!; + + const damping = 0.92; // Ease-out damping (lower = faster decay) + const maxVelocity = 0.5; // Increased for stronger repulsion effects + + // Continuous mouse repulsion (weaker, always active) + const mouseRepulsionRadius = 2; + const mouseRepulsionStrength = 0.0003; // Much weaker than click repulsion + + for (let i = 0; i < positions.length; i += 3) { + const x = positions[i]; + const y = positions[i + 1]; + const z = positions[i + 2]; + + // Calculate distance from mouse + const dx = x - this.mouseWorldX; + const dy = y - this.mouseWorldY; + const distance = Math.sqrt(dx * dx + dy * dy); + + // Apply continuous mouse repulsion if within radius + if (distance < mouseRepulsionRadius && distance > 0.1) { + const normalizedDx = dx / distance; + const normalizedDy = dy / distance; + const force = (1 - distance / mouseRepulsionRadius) * mouseRepulsionStrength; + + // Add weak force to velocity + velocities[i] += normalizedDx * force; + velocities[i + 1] += normalizedDy * force; + velocities[i + 2] += (Math.random() - 0.5) * force * 0.2; + } + + // Apply damping (ease-out effect) + velocities[i] *= damping; + velocities[i + 1] *= damping; + velocities[i + 2] *= damping; + + // Limit max velocity + const vx = Math.max(-maxVelocity, Math.min(maxVelocity, velocities[i])); + const vy = Math.max(-maxVelocity, Math.min(maxVelocity, velocities[i + 1])); + const vz = Math.max(-maxVelocity, Math.min(maxVelocity, velocities[i + 2])); + + velocities[i] = vx; + velocities[i + 1] = vy; + velocities[i + 2] = vz; + + // Update position based on velocity + positions[i] += velocities[i]; + positions[i + 1] += velocities[i + 1]; + positions[i + 2] += velocities[i + 2]; + + // Slower vertical drift + positions[i + 1] += 0.001; + + // Wrap around boundaries + if (positions[i] > 10) positions[i] = -10; + if (positions[i] < -10) positions[i] = 10; + if (positions[i + 1] > 10) positions[i + 1] = -10; + if (positions[i + 1] < -10) positions[i + 1] = 10; + if (positions[i + 2] > 10) positions[i + 2] = -10; + if (positions[i + 2] < -10) positions[i + 2] = 10; + } + this.particles.geometry.attributes['position'].needsUpdate = true; + } + + // Update and render repulsion waves + this.updateRepulsionWaves(); + + // Camera follows mouse slightly + this.camera.position.x += (this.mouseX * 0.3 - this.camera.position.x) * 0.03; + this.camera.position.y += (this.mouseY * 0.3 - this.camera.position.y) * 0.03; + this.camera.lookAt(0, 0, 0); + + this.renderer.render(this.scene, this.camera); + }; + + private updateRepulsionWaves(): void { + const now = Date.now(); + const waveDuration = 1500; // Longer, softer animation duration + + // Update existing waves + for (let i = this.repulsionWaves.length - 1; i >= 0; i--) { + const wave = this.repulsionWaves[i]; + const elapsed = now - wave.time; + const progress = Math.min(elapsed / waveDuration, 1); + + if (progress >= 1) { + // Remove expired waves + if (this.waveObjects[i]) { + this.scene.remove(this.waveObjects[i]); + this.waveObjects[i].geometry.dispose(); + (this.waveObjects[i].material as THREE.Material).dispose(); + } + this.waveObjects.splice(i, 1); + this.repulsionWaves.splice(i, 1); + continue; + } + + // Smooth easing function for softer expansion + const easeOut = 1 - Math.pow(1 - progress, 3); + + // Expand radius with easing + wave.radius = wave.maxRadius * easeOut; + + // Softer fade out - start from initial opacity + const initialOpacity = wave.opacity; + wave.opacity = initialOpacity * (1 - progress * progress); // Quadratic fade for softer effect + + // Create or update wave object + if (!this.waveObjects[i]) { + this.createWaveObject(wave, i); + } else { + this.updateWaveObject(wave, i); + } + } + } + + private createWaveObject(wave: { x: number; y: number; radius: number; maxRadius: number; opacity: number }, index: number): void { + const geometry = new THREE.RingGeometry(0, 0.05, 32); + const material = new THREE.MeshBasicMaterial({ + color: 0xffffff, + transparent: true, + opacity: wave.opacity * 0.5, // Softer initial opacity + side: THREE.DoubleSide, + blending: THREE.AdditiveBlending, + }); + + const waveMesh = new THREE.Mesh(geometry, material); + waveMesh.position.set(wave.x, wave.y, 0); + this.scene.add(waveMesh); + this.waveObjects[index] = waveMesh; + } + + private updateWaveObject(wave: { x: number; y: number; radius: number; maxRadius: number; opacity: number }, index: number): void { + const waveMesh = this.waveObjects[index]; + if (!waveMesh) return; + + const material = waveMesh.material as THREE.MeshBasicMaterial; + const progress = wave.radius / wave.maxRadius; + + // Update geometry for expanding ring - thinner ring for softer look + waveMesh.geometry.dispose(); + const innerRadius = Math.max(0, wave.radius * 0.85); // Thinner ring (85% instead of 70%) + waveMesh.geometry = new THREE.RingGeometry(innerRadius, wave.radius, 32); + + // Softer opacity + material.opacity = wave.opacity * 0.6; // Additional softening multiplier + + // Softer color variation (subtle blue to purple gradient) + const hue = (progress * 40 + 240) % 360; // Slower color transition + material.color.setHSL(hue / 360, 0.5, 0.8); // Lower saturation, higher lightness for softer look + } + + private onWindowResize(): void { + const width = window.innerWidth; + const height = window.innerHeight; + + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(width, height); + } +} + diff --git a/src/app/shared/components/progress-card/progress-card.component.ts b/src/app/shared/components/progress-card/progress-card.component.ts new file mode 100644 index 0000000..b403e25 --- /dev/null +++ b/src/app/shared/components/progress-card/progress-card.component.ts @@ -0,0 +1,232 @@ +import { Component, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Activity } from '../../../core/models/sport-goal.model'; + +@Component({ + selector: 'app-progress-card', + standalone: true, + imports: [CommonModule], + template: ` +
+
+

+ {{ emoji }} + {{ title }} +

+ + {{ percentage.toFixed(1) }}% + +
+ +
+
+ {{ currentLabel }} + {{ targetLabel }} +
+
+
+
+
+
+
+ +
+

{{ description }}

+
+ +
+
+ + + Schedule status: + + + + {{ daysAheadOrBehind.toFixed(0) }} day{{ daysAheadOrBehind !== 1 ? 's' : '' }} behind + + + {{ Math.abs(daysAheadOrBehind).toFixed(0) }} day{{ Math.abs(daysAheadOrBehind) !== 1 ? 's' : '' }} ahead + + + ✨ On schedule + + +
+
+ + 📊 + Current daily avg: + + + {{ currentDailyAvg.toFixed(2) }} km/day + +
+
+ + + Optimal daily avg: + + + {{ optimalDailyAvg.toFixed(2) }} km/day + +
+
+ + 📅 + Est. completion: + + + {{ estimatedCompletion }} + +
+
+ + 🎯 + Daily needed: + + + {{ dailyKmNeeded.toFixed(2) }} km/day + +
+
+ +
+

+ 🏃 + Last 3 Trainings +

+
+
+
+
+

+ {{ activity.name }} +

+
+ + 📅 + {{ formatActivityDate(activity.startDate) }} + + + 📏 + {{ formatDistance(activity.distance) }} km + + + ⏱️ + {{ formatTime(activity.movingTime) }} + +
+
+
+
+
+
+
+ `, + styles: [ + ` + @keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } + } + .animate-shimmer { + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.2) 50%, + transparent 100% + ); + background-size: 200% 100%; + animation: shimmer 5s infinite; + } + `, + ], +}) +export class ProgressCardComponent { + @Input() title = ''; + @Input() percentage = 0; + @Input() currentLabel = ''; + @Input() targetLabel = ''; + @Input() description = ''; + @Input() estimatedCompletion: string | null = null; + @Input() dailyKmNeeded: number | null = null; + @Input() currentDailyAvg: number | null = null; + @Input() optimalDailyAvg: number | null = null; + @Input() daysAheadOrBehind: number | null = null; + @Input() recentActivities: Activity[] | null = null; + @Input() emoji = '🏃'; + + readonly Math = Math; + + formatDistance(meters: number): string { + return (meters / 1000).toFixed(2); + } + + formatTime(seconds: number): string { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } + return `${minutes}m`; + } + + formatActivityDate(date: Date | string): string { + const activityDate = typeof date === 'string' ? new Date(date) : date; + const now = new Date(); + + // Normalize both dates to midnight (start of day) for accurate day comparison + const activityDay = new Date( + activityDate.getFullYear(), + activityDate.getMonth(), + activityDate.getDate() + ); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + + const diffTime = today.getTime() - activityDay.getTime(); + const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) { + return 'Today'; + } else if (diffDays === 1) { + return 'Yesterday'; + } else if (diffDays < 7) { + return `${diffDays} days ago`; + } else { + return activityDate.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: activityDate.getFullYear() !== now.getFullYear() ? 'numeric' : undefined, + }); + } + } +} + diff --git a/src/app/shared/components/progress-ring/progress-ring.component.ts b/src/app/shared/components/progress-ring/progress-ring.component.ts new file mode 100644 index 0000000..7802ec2 --- /dev/null +++ b/src/app/shared/components/progress-ring/progress-ring.component.ts @@ -0,0 +1,82 @@ +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-progress-ring', + standalone: true, + imports: [CommonModule], + template: ` +
+ + + + + + +
+
+
+ {{ percentage.toFixed(0) }}% +
+
{{ label }}
+
+
+
+ `, + styles: [], +}) +export class ProgressRingComponent implements OnChanges { + @Input() percentage: number = 0; + @Input() size: number = 120; + @Input() strokeWidth: number = 8; + @Input() progressColor: string = '#3b82f6'; + @Input() backgroundColor: string = '#e5e7eb'; + @Input() label: string = ''; + + radius: number = 0; + center: number = 0; + circumference: number = 0; + offset: number = 0; + + ngOnChanges(changes: SimpleChanges): void { + this.radius = (this.size - this.strokeWidth) / 2; + this.center = this.size / 2; + this.circumference = 2 * Math.PI * this.radius; + this.offset = this.circumference - (this.percentage / 100) * this.circumference; + } +} + + + + + + + + + + + + + + + + diff --git a/src/app/shared/components/space-invaders/space-invaders.component.ts b/src/app/shared/components/space-invaders/space-invaders.component.ts new file mode 100644 index 0000000..ef196e5 --- /dev/null +++ b/src/app/shared/components/space-invaders/space-invaders.component.ts @@ -0,0 +1,367 @@ +import { + Component, + OnInit, + OnDestroy, + ElementRef, + ViewChild, + AfterViewInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; + +interface Bullet { + x: number; + y: number; + speed: number; +} + +interface Enemy { + x: number; + y: number; + width: number; + height: number; + alive: boolean; +} + +@Component({ + selector: 'app-space-invaders', + standalone: true, + imports: [CommonModule], + template: ` +
+ +
+ Score: {{ score }} | Lives: {{ lives }} +
+
+ ← → to move • Space to shoot +
+ @if (gameOver) { +
+
+

Game Over!

+

Final Score: {{ score }}

+ +
+
+ } +
+ `, + styles: [ + ` + .space-invaders-container { + position: relative; + width: 100%; + height: 100%; + background: #000000; + border-radius: 1rem; + overflow: hidden; + } + canvas { + display: block; + } + `, + ], +}) +export class SpaceInvadersComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('canvas', { static: false }) canvasRef!: ElementRef; + + private ctx!: CanvasRenderingContext2D; + private animationId: number | null = null; + private player = { x: 0, y: 0, width: 50, height: 30, speed: 5 }; + private bullets: Bullet[] = []; + private enemies: Enemy[] = []; + private enemyBullets: Bullet[] = []; + private keys: { [key: string]: boolean } = {}; + private lastShot = 0; + private enemyDirection = 1; + private enemySpeed = 1; + private lastEnemyShot = 0; + score = 0; + lives = 3; + gameOver = false; + + ngOnInit(): void {} + + ngAfterViewInit(): void { + const canvas = this.canvasRef.nativeElement; + this.ctx = canvas.getContext('2d')!; + this.resizeCanvas(); + this.setupEventListeners(); + this.initGame(); + this.animate(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + this.removeEventListeners(); + } + + private resizeCanvas(): void { + const canvas = this.canvasRef.nativeElement; + const container = canvas.parentElement!; + canvas.width = container.clientWidth; + canvas.height = container.clientHeight; + this.player.x = canvas.width / 2 - this.player.width / 2; + this.player.y = canvas.height - 50; + } + + private initGame(): void { + this.enemies = []; + this.bullets = []; + this.enemyBullets = []; + this.score = 0; + this.lives = 3; + this.gameOver = false; + this.enemyDirection = 1; + this.enemySpeed = 1; + + // Create enemies in grid + const rows = 5; + const cols = 10; + const spacing = 60; + const startX = 50; + const startY = 50; + + for (let row = 0; row < rows; row++) { + for (let col = 0; col < cols; col++) { + this.enemies.push({ + x: startX + col * spacing, + y: startY + row * spacing, + width: 40, + height: 30, + alive: true, + }); + } + } + } + + private setupEventListeners(): void { + window.addEventListener('keydown', (e) => { + this.keys[e.key] = true; + if (e.key === ' ' && !this.gameOver) { + e.preventDefault(); + this.shoot(); + } + }); + + window.addEventListener('keyup', (e) => { + this.keys[e.key] = false; + }); + + window.addEventListener('resize', () => this.resizeCanvas()); + } + + private removeEventListeners(): void { + window.removeEventListener('keydown', () => {}); + window.removeEventListener('keyup', () => {}); + window.removeEventListener('resize', () => {}); + } + + private updatePlayer(): void { + if (this.keys['ArrowLeft'] && this.player.x > 0) { + this.player.x -= this.player.speed; + } + if (this.keys['ArrowRight'] && this.player.x < this.canvasRef.nativeElement.width - this.player.width) { + this.player.x += this.player.speed; + } + } + + private shoot(): void { + const now = Date.now(); + if (now - this.lastShot > 200) { + this.bullets.push({ + x: this.player.x + this.player.width / 2, + y: this.player.y, + speed: -8, + }); + this.lastShot = now; + } + } + + private updateBullets(): void { + // Player bullets + this.bullets = this.bullets.filter((bullet) => { + bullet.y += bullet.speed; + return bullet.y > 0; + }); + + // Enemy bullets + this.enemyBullets = this.enemyBullets.filter((bullet) => { + bullet.y += bullet.speed; + return bullet.y < this.canvasRef.nativeElement.height; + }); + } + + private updateEnemies(): void { + if (this.enemies.length === 0) { + this.initGame(); + this.enemySpeed += 0.5; + return; + } + + // Move enemies + let shouldMoveDown = false; + for (const enemy of this.enemies) { + if (!enemy.alive) continue; + enemy.x += this.enemyDirection * this.enemySpeed; + if (enemy.x <= 0 || enemy.x >= this.canvasRef.nativeElement.width - enemy.width) { + shouldMoveDown = true; + } + } + + if (shouldMoveDown) { + this.enemyDirection *= -1; + for (const enemy of this.enemies) { + if (enemy.alive) { + enemy.y += 20; + if (enemy.y > this.player.y) { + this.gameOver = true; + } + } + } + } + + // Enemy shooting + const now = Date.now(); + if (now - this.lastEnemyShot > 1000 && this.enemies.some((e) => e.alive)) { + const aliveEnemies = this.enemies.filter((e) => e.alive); + if (aliveEnemies.length > 0) { + const randomEnemy = aliveEnemies[Math.floor(Math.random() * aliveEnemies.length)]; + this.enemyBullets.push({ + x: randomEnemy.x + randomEnemy.width / 2, + y: randomEnemy.y + randomEnemy.height, + speed: 3, + }); + this.lastEnemyShot = now; + } + } + } + + private checkCollisions(): void { + // Player bullets vs enemies + for (const bullet of this.bullets) { + for (const enemy of this.enemies) { + if ( + enemy.alive && + bullet.x > enemy.x && + bullet.x < enemy.x + enemy.width && + bullet.y > enemy.y && + bullet.y < enemy.y + enemy.height + ) { + enemy.alive = false; + this.bullets = this.bullets.filter((b) => b !== bullet); + this.score += 10; + break; + } + } + } + + // Enemy bullets vs player + for (const bullet of this.enemyBullets) { + if ( + bullet.x > this.player.x && + bullet.x < this.player.x + this.player.width && + bullet.y > this.player.y && + bullet.y < this.player.y + this.player.height + ) { + this.enemyBullets = this.enemyBullets.filter((b) => b !== bullet); + this.lives--; + if (this.lives <= 0) { + this.gameOver = true; + } + } + } + } + + private draw(): void { + const canvas = this.canvasRef.nativeElement; + const ctx = this.ctx; + + // Clear canvas + ctx.fillStyle = '#000000'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Draw stars + ctx.fillStyle = '#ffffff'; + for (let i = 0; i < 50; i++) { + const x = (i * 37) % canvas.width; + const y = (i * 53) % canvas.height; + ctx.fillRect(x, y, 2, 2); + } + + // Draw player + ctx.fillStyle = '#00ff00'; + ctx.beginPath(); + ctx.moveTo(this.player.x + this.player.width / 2, this.player.y); + ctx.lineTo(this.player.x, this.player.y + this.player.height); + ctx.lineTo(this.player.x + this.player.width, this.player.y + this.player.height); + ctx.closePath(); + ctx.fill(); + + // Draw bullets + ctx.fillStyle = '#ffff00'; + for (const bullet of this.bullets) { + ctx.fillRect(bullet.x - 2, bullet.y, 4, 10); + } + + // Draw enemy bullets + ctx.fillStyle = '#ff0000'; + for (const bullet of this.enemyBullets) { + ctx.fillRect(bullet.x - 2, bullet.y, 4, 10); + } + + // Draw enemies + ctx.fillStyle = '#ff00ff'; + for (const enemy of this.enemies) { + if (enemy.alive) { + ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height); + // Draw eyes + ctx.fillStyle = '#ffffff'; + ctx.fillRect(enemy.x + 8, enemy.y + 8, 6, 6); + ctx.fillRect(enemy.x + enemy.width - 14, enemy.y + 8, 6, 6); + ctx.fillStyle = '#ff00ff'; + } + } + } + + private update(): void { + if (this.gameOver) return; + + this.updatePlayer(); + this.updateBullets(); + this.updateEnemies(); + this.checkCollisions(); + } + + private animate = (): void => { + this.update(); + this.draw(); + this.animationId = requestAnimationFrame(this.animate); + }; + + restart(): void { + this.initGame(); + } +} + + + + + + + + + + + + + + + diff --git a/src/app/shared/components/three-geometric/three-geometric.component.ts b/src/app/shared/components/three-geometric/three-geometric.component.ts new file mode 100644 index 0000000..5f45869 --- /dev/null +++ b/src/app/shared/components/three-geometric/three-geometric.component.ts @@ -0,0 +1,258 @@ +import { + Component, + OnInit, + OnDestroy, + ElementRef, + ViewChild, + Input, + AfterViewInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import * as THREE from 'three'; + +@Component({ + selector: 'app-three-geometric', + standalone: true, + imports: [CommonModule], + template: ` + + `, + styles: [ + ` + canvas { + display: block; + width: 100%; + height: 100%; + } + `, + ], +}) +export class ThreeGeometricComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('canvas', { static: false }) canvasRef!: ElementRef; + @Input() type: 'floating' | 'rings' | 'particles' = 'floating'; + @Input() color: string = '#3b82f6'; + + private scene!: THREE.Scene; + private camera!: THREE.PerspectiveCamera; + private renderer!: THREE.WebGLRenderer; + private animationId: number | null = null; + private objects: THREE.Object3D[] = []; + + ngOnInit(): void {} + + ngAfterViewInit(): void { + this.initThree(); + this.createGeometry(); + this.animate(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + this.objects.forEach((obj) => { + if (obj instanceof THREE.Mesh) { + obj.geometry.dispose(); + if (Array.isArray(obj.material)) { + obj.material.forEach((m) => m.dispose()); + } else { + obj.material.dispose(); + } + } + }); + if (this.renderer) { + this.renderer.dispose(); + } + } + + private initThree(): void { + const canvas = this.canvasRef.nativeElement; + const width = canvas.clientWidth; + const height = canvas.clientHeight; + + this.scene = new THREE.Scene(); + this.scene.background = null; + + this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); + this.camera.position.z = 5; + + this.renderer = new THREE.WebGLRenderer({ + canvas, + alpha: true, + antialias: true, + }); + this.renderer.setSize(width, height); + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + + window.addEventListener('resize', () => this.onWindowResize()); + } + + private createGeometry(): void { + const color = new THREE.Color(this.color); + + if (this.type === 'floating') { + this.createFloatingShapes(color); + } else if (this.type === 'rings') { + this.createRings(color); + } else if (this.type === 'particles') { + this.createParticles(color); + } + } + + private createFloatingShapes(color: THREE.Color): void { + // Create floating geometric shapes + const shapes = [ + new THREE.BoxGeometry(0.5, 0.5, 0.5), + new THREE.SphereGeometry(0.3, 16, 16), + new THREE.OctahedronGeometry(0.4, 0), + new THREE.TetrahedronGeometry(0.4, 0), + ]; + + for (let i = 0; i < 8; i++) { + const geometry = shapes[i % shapes.length]; + const material = new THREE.MeshStandardMaterial({ + color: color.clone().multiplyScalar(0.8 + Math.random() * 0.4), + transparent: true, + opacity: 0.3, + metalness: 0.7, + roughness: 0.3, + }); + + const mesh = new THREE.Mesh(geometry, material); + mesh.position.set( + (Math.random() - 0.5) * 8, + (Math.random() - 0.5) * 8, + (Math.random() - 0.5) * 5 + ); + mesh.rotation.set( + Math.random() * Math.PI, + Math.random() * Math.PI, + Math.random() * Math.PI + ); + + (mesh.userData as any).speed = { + x: (Math.random() - 0.5) * 0.01, + y: (Math.random() - 0.5) * 0.01, + z: (Math.random() - 0.5) * 0.01, + rotX: (Math.random() - 0.5) * 0.02, + rotY: (Math.random() - 0.5) * 0.02, + rotZ: (Math.random() - 0.5) * 0.02, + }; + + this.scene.add(mesh); + this.objects.push(mesh); + } + + // Add ambient light + const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); + this.scene.add(ambientLight); + + const pointLight = new THREE.PointLight(color, 1, 100); + pointLight.position.set(5, 5, 5); + this.scene.add(pointLight); + } + + private createRings(color: THREE.Color): void { + // Create rotating rings + for (let i = 0; i < 3; i++) { + const geometry = new THREE.TorusGeometry(1 + i * 0.5, 0.05, 8, 50); + const material = new THREE.MeshStandardMaterial({ + color: color.clone().multiplyScalar(0.6 + i * 0.2), + transparent: true, + opacity: 0.4, + metalness: 0.8, + roughness: 0.2, + }); + + const ring = new THREE.Mesh(geometry, material); + ring.rotation.x = Math.PI / 2; + ring.position.z = -i * 0.5; + + (ring.userData as any).speed = 0.005 + i * 0.002; + + this.scene.add(ring); + this.objects.push(ring); + } + + const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); + this.scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(color, 0.8); + directionalLight.position.set(5, 5, 5); + this.scene.add(directionalLight); + } + + private createParticles(color: THREE.Color): void { + const geometry = new THREE.BufferGeometry(); + const count = 200; + const positions = new Float32Array(count * 3); + + for (let i = 0; i < count * 3; i++) { + positions[i] = (Math.random() - 0.5) * 10; + } + + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + + const material = new THREE.PointsMaterial({ + color: color, + size: 0.1, + transparent: true, + opacity: 0.6, + }); + + const points = new THREE.Points(geometry, material); + this.scene.add(points); + this.objects.push(points); + } + + private animate = (): void => { + this.animationId = requestAnimationFrame(this.animate); + + this.objects.forEach((obj) => { + if (obj instanceof THREE.Mesh) { + const userData = obj.userData as any; + if (userData.speed) { + if (userData.speed.x !== undefined) { + obj.position.x += userData.speed.x; + obj.position.y += userData.speed.y; + obj.position.z += userData.speed.z; + obj.rotation.x += userData.speed.rotX; + obj.rotation.y += userData.speed.rotY; + obj.rotation.z += userData.speed.rotZ; + } else if (userData.speed !== undefined) { + obj.rotation.y += userData.speed; + } + } + } else if (obj instanceof THREE.Points) { + obj.rotation.y += 0.001; + } + }); + + this.renderer.render(this.scene, this.camera); + }; + + private onWindowResize(): void { + const canvas = this.canvasRef.nativeElement; + const width = canvas.clientWidth; + const height = canvas.clientHeight; + + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(width, height); + } +} + + + + + + + + + + + + + + + diff --git a/src/app/shared/components/three-trophy/three-trophy.component.ts b/src/app/shared/components/three-trophy/three-trophy.component.ts new file mode 100644 index 0000000..51d1ff3 --- /dev/null +++ b/src/app/shared/components/three-trophy/three-trophy.component.ts @@ -0,0 +1,241 @@ +import { + Component, + OnInit, + OnDestroy, + ElementRef, + ViewChild, + Input, + AfterViewInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import * as THREE from 'three'; + +@Component({ + selector: 'app-three-trophy', + standalone: true, + imports: [CommonModule], + template: ` +
+ +
+ {{ progress.toFixed(1) }}% +
+
+ `, + styles: [ + ` + canvas { + display: block; + outline: none; + } + `, + ], +}) +export class ThreeTrophyComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('canvas', { static: false }) canvasRef!: ElementRef; + @Input() progress: number | null = null; + @Input() size: number = 200; + + private scene!: THREE.Scene; + private camera!: THREE.PerspectiveCamera; + private renderer!: THREE.WebGLRenderer; + private trophy!: THREE.Group; + private animationId: number | null = null; + private mouseX = 0; + private mouseY = 0; + + ngOnInit(): void {} + + ngAfterViewInit(): void { + this.initThree(); + this.createTrophy(); + this.animate(); + this.addMouseInteraction(); + } + + ngOnDestroy(): void { + if (this.animationId !== null) { + cancelAnimationFrame(this.animationId); + } + if (this.renderer) { + this.renderer.dispose(); + } + } + + private initThree(): void { + const canvas = this.canvasRef.nativeElement; + const width = canvas.clientWidth; + const height = canvas.clientHeight; + + // Scene + this.scene = new THREE.Scene(); + this.scene.background = null; + + // Camera + this.camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1000); + this.camera.position.set(0, 2, 5); + this.camera.lookAt(0, 0, 0); + + // Renderer + this.renderer = new THREE.WebGLRenderer({ + canvas: canvas, + antialias: true, + alpha: true, + }); + this.renderer.setSize(width, height); + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + this.renderer.shadowMap.enabled = true; + this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; + + // Lights + const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); + this.scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); + directionalLight.position.set(5, 10, 5); + directionalLight.castShadow = true; + this.scene.add(directionalLight); + + const pointLight = new THREE.PointLight(0x4f46e5, 1, 100); + pointLight.position.set(-5, 5, 5); + this.scene.add(pointLight); + + // Handle resize + window.addEventListener('resize', () => this.onWindowResize()); + } + + private createTrophy(): void { + this.trophy = new THREE.Group(); + + // Trophy base + const baseGeometry = new THREE.CylinderGeometry(0.8, 1, 0.3, 32); + const baseMaterial = new THREE.MeshStandardMaterial({ + color: 0xffd700, + metalness: 0.8, + roughness: 0.2, + }); + const base = new THREE.Mesh(baseGeometry, baseMaterial); + base.position.y = -1.5; + base.castShadow = true; + base.receiveShadow = true; + this.trophy.add(base); + + // Trophy stem + const stemGeometry = new THREE.CylinderGeometry(0.15, 0.15, 1.5, 16); + const stemMaterial = new THREE.MeshStandardMaterial({ + color: 0xffd700, + metalness: 0.8, + roughness: 0.2, + }); + const stem = new THREE.Mesh(stemGeometry, stemMaterial); + stem.position.y = -0.5; + stem.castShadow = true; + this.trophy.add(stem); + + // Trophy cup (bottom) + const cupBottomGeometry = new THREE.ConeGeometry(0.6, 0.8, 32); + const cupMaterial = new THREE.MeshStandardMaterial({ + color: 0xffd700, + metalness: 0.9, + roughness: 0.1, + }); + const cupBottom = new THREE.Mesh(cupBottomGeometry, cupMaterial); + cupBottom.position.y = 0.3; + cupBottom.rotation.x = Math.PI; + cupBottom.castShadow = true; + this.trophy.add(cupBottom); + + // Trophy cup (top rim) + const cupTopGeometry = new THREE.TorusGeometry(0.6, 0.05, 16, 32); + const cupTop = new THREE.Mesh(cupTopGeometry, cupMaterial); + cupTop.position.y = 0.7; + cupTop.castShadow = true; + this.trophy.add(cupTop); + + // Trophy handles + const handleGeometry = new THREE.TorusGeometry(0.3, 0.05, 8, 16); + const handle1 = new THREE.Mesh(handleGeometry, cupMaterial); + handle1.position.set(0.6, 0.5, 0); + handle1.rotation.z = Math.PI / 2; + handle1.castShadow = true; + this.trophy.add(handle1); + + const handle2 = new THREE.Mesh(handleGeometry, cupMaterial); + handle2.position.set(-0.6, 0.5, 0); + handle2.rotation.z = -Math.PI / 2; + handle2.castShadow = true; + this.trophy.add(handle2); + + // Progress indicator - particles + if (this.progress !== null) { + const particleCount = Math.floor((this.progress / 100) * 50); + const particles = new THREE.BufferGeometry(); + const positions = new Float32Array(particleCount * 3); + + for (let i = 0; i < particleCount; i++) { + const angle = (i / particleCount) * Math.PI * 2; + const radius = 0.8; + positions[i * 3] = Math.cos(angle) * radius; + positions[i * 3 + 1] = 0.7 + Math.sin(angle) * 0.3; + positions[i * 3 + 2] = Math.sin(angle) * radius; + } + + particles.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + + const particleMaterial = new THREE.PointsMaterial({ + color: 0x4f46e5, + size: 0.05, + transparent: true, + opacity: 0.8, + }); + + const particleSystem = new THREE.Points(particles, particleMaterial); + this.trophy.add(particleSystem); + } + + this.scene.add(this.trophy); + } + + private addMouseInteraction(): void { + const canvas = this.canvasRef.nativeElement; + canvas.addEventListener('mousemove', (event) => { + const rect = canvas.getBoundingClientRect(); + this.mouseX = ((event.clientX - rect.left) / rect.width) * 2 - 1; + this.mouseY = -((event.clientY - rect.top) / rect.height) * 2 + 1; + }); + + canvas.addEventListener('mouseleave', () => { + this.mouseX = 0; + this.mouseY = 0; + }); + } + + private animate = (): void => { + this.animationId = requestAnimationFrame(this.animate); + + // Rotate trophy - much slower + if (this.trophy) { + this.trophy.rotation.y += 0.001; + + // Subtle mouse interaction + this.trophy.rotation.y += this.mouseX * 0.01; + this.trophy.rotation.x = this.mouseY * 0.15; + } + + this.renderer.render(this.scene, this.camera); + }; + + private onWindowResize(): void { + const canvas = this.canvasRef.nativeElement; + const width = canvas.clientWidth; + const height = canvas.clientHeight; + + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(width, height); + } +} + diff --git a/src/app/shared/utils/date.utils.ts b/src/app/shared/utils/date.utils.ts new file mode 100644 index 0000000..cf7d5c2 --- /dev/null +++ b/src/app/shared/utils/date.utils.ts @@ -0,0 +1,83 @@ +/** + * Check if a date is within the year 2026 + */ +export function isIn2026(date: Date): boolean { + return date.getFullYear() === 2026; +} + +/** + * Get the start date of 2026 + */ +export function get2026Start(): Date { + return new Date('2026-01-01T00:00:00Z'); +} + +/** + * Get the end date of 2026 + */ +export function get2026End(): Date { + return new Date('2026-12-31T23:59:59Z'); +} + +/** + * Get days elapsed in 2026 + */ +export function getDaysElapsedIn2026(): number { + const now = new Date(); + const start = get2026Start(); + const diff = now.getTime() - start.getTime(); + return Math.floor(diff / (1000 * 60 * 60 * 24)); +} + +/** + * Get total days in 2026 + */ +export function getTotalDaysIn2026(): number { + const start = get2026Start(); + const end = get2026End(); + const diff = end.getTime() - start.getTime(); + return Math.floor(diff / (1000 * 60 * 60 * 24)) + 1; +} + +/** + * Calculate ideal progress percentage based on linear progression + */ +export function getIdealProgress(): number { + const elapsed = getDaysElapsedIn2026(); + const total = getTotalDaysIn2026(); + return Math.min(100, (elapsed / total) * 100); +} + +/** + * Format date to readable string + */ +export function formatDate(date: Date): string { + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + }); +} + +/** + * Convert meters to kilometers + */ +export function metersToKm(meters: number): number { + return meters / 1000; +} + + + + + + + + + + + + + + + + diff --git a/src/assets/gaming/logos/README.md b/src/assets/gaming/logos/README.md new file mode 100644 index 0000000..fe29f78 --- /dev/null +++ b/src/assets/gaming/logos/README.md @@ -0,0 +1,13 @@ +# Gaming Logos + +Logo files in this folder: + +- `tft.svg` ✓ - Teamfight Tactics logo +- `lol.svg` ✓ - League of Legends logo +- `rl.svg` ✓ - Rocket League logo +- `faceit.svg` ✓ - Faceit logo + +All logos are loaded and ready to use! + +Supported formats: SVG (recommended) or PNG + diff --git a/src/assets/gaming/logos/faceit.svg b/src/assets/gaming/logos/faceit.svg new file mode 100644 index 0000000..e8afd54 --- /dev/null +++ b/src/assets/gaming/logos/faceit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/gaming/logos/lol.svg b/src/assets/gaming/logos/lol.svg new file mode 100644 index 0000000..7901d47 --- /dev/null +++ b/src/assets/gaming/logos/lol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/gaming/logos/rl.svg b/src/assets/gaming/logos/rl.svg new file mode 100644 index 0000000..747bdd7 --- /dev/null +++ b/src/assets/gaming/logos/rl.svg @@ -0,0 +1 @@ + image/svg+xml \ No newline at end of file diff --git a/src/assets/gaming/logos/tft.svg b/src/assets/gaming/logos/tft.svg new file mode 100644 index 0000000..5c50398 --- /dev/null +++ b/src/assets/gaming/logos/tft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts new file mode 100644 index 0000000..7baa523 --- /dev/null +++ b/src/environments/environment.prod.ts @@ -0,0 +1,42 @@ +/** + * Production environment. + * Non-secret config from process.env at build time. + * API keys stay in backend .env only. + */ +declare const process: { env: Record }; + +export const environment = { + production: true, + strava: { + clientId: process.env['STRAVA_CLIENT_ID'] || '', + redirectUri: process.env['STRAVA_REDIRECT_URI'] || '', + accessToken: '', + refreshToken: '', + tokenExpiresAt: null as number | null, + }, + riot: { + region: process.env['RIOT_REGION'] || 'eun1', + summonerNames: { + lol: process.env['RIOT_SUMMONER_NAME_LOL'] || '', + tft: process.env['RIOT_SUMMONER_NAME_TFT'] || '', + }, + tagLine: process.env['RIOT_TAG_LINE'] || '', + }, + faceit: { + userId: process.env['FACEIT_USER_ID'] || '', + }, + tracker: { + rocketLeague: { + platform: process.env['ROCKET_LEAGUE_PLATFORM'] || 'steam', + username: process.env['ROCKET_LEAGUE_USERNAME'] || '', + }, + }, + goals: { + sport: { + bike: 7500, + run: 2500, + swim: 250, + }, + endDate: new Date('2026-12-31'), + }, +}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100644 index 0000000..ba853f8 --- /dev/null +++ b/src/environments/environment.ts @@ -0,0 +1,40 @@ +/** + * Development environment. + * No secrets - API keys are handled by the backend proxy. + * Copy .env.example to .env and configure for local backend. + */ +export const environment = { + production: false, + strava: { + clientId: '196635', + redirectUri: 'http://localhost:4000/auth/strava/callback', + accessToken: '', + refreshToken: '', + tokenExpiresAt: null as number | null, + }, + riot: { + region: 'eun1', + summonerNames: { + lol: 'R4K4N1', + tft: 'R4K4N1', + }, + tagLine: 'EUNE', + }, + faceit: { + userId: 'rakani', + }, + tracker: { + rocketLeague: { + platform: 'steam', + username: 'rakani32', + }, + }, + goals: { + sport: { + bike: 7500, + run: 2500, + swim: 250, + }, + endDate: new Date('2026-12-31'), + }, +}; diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100644 index 0000000..16c67e5 --- /dev/null +++ b/src/favicon.ico @@ -0,0 +1,16 @@ +placeholder + + + + + + + + + + + + + + + diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..b5cf206 --- /dev/null +++ b/src/index.html @@ -0,0 +1,29 @@ + + + + + Goals Tracker 2026 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main.server.ts b/src/main.server.ts new file mode 100644 index 0000000..d0c4509 --- /dev/null +++ b/src/main.server.ts @@ -0,0 +1,17 @@ +export { AppServerModule as default } from './app/app.server'; + + + + + + + + + + + + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..fbbb582 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,23 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; +import { appConfig } from './app/app.config'; + +bootstrapApplication(AppComponent, appConfig).catch((err) => + console.error(err) +); + + + + + + + + + + + + + + + + diff --git a/src/styles.scss b/src/styles.scss new file mode 100644 index 0000000..842a49a --- /dev/null +++ b/src/styles.scss @@ -0,0 +1,86 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +/* Global Animations */ +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-10px); + } +} + +.animate-fade-in { + animation: fade-in 0.6s ease-out; +} + +.animate-shimmer { + animation: shimmer 2s infinite; +} + +.animate-float { + animation: float 3s ease-in-out infinite; +} + +/* Smooth scrolling */ +html { + scroll-behavior: smooth; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(to bottom, #3b82f6, #8b5cf6); + border-radius: 5px; +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(to bottom, #2563eb, #7c3aed); +} + +/* Hide default cursor globally - only on desktop */ +@media (hover: hover) and (pointer: fine) { + * { + cursor: none !important; + } + + a, button, [role="button"], input, textarea, select { + cursor: none !important; + } +} + diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..ea05718 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,26 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{html,ts}", + ], + theme: { + extend: {}, + }, + plugins: [], +} + + + + + + + + + + + + + + + + diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..d43e920 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"], + "exclude": [ + "node_modules", + "node_modules/**/*.ts", + "node_modules/rocketleaguetrackerapi/**/*.ts" + ] +} + + + + + + + + + + + + + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1dac22a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "bundler", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "lib": ["ES2022", "dom"] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} + diff --git a/tsconfig.server.json b/tsconfig.server.json new file mode 100644 index 0000000..f568b35 --- /dev/null +++ b/tsconfig.server.json @@ -0,0 +1,27 @@ +{ + "extends": "./tsconfig.app.json", + "compilerOptions": { + "outDir": "./out-tsc/server", + "types": ["node"] + }, + "files": ["src/main.server.ts", "server.ts"], + "include": ["src/**/*.d.ts"], + "exclude": [ + "node_modules", + "node_modules/**/*.ts", + "node_modules/rocketleaguetrackerapi/**/*.ts" + ] +} + + + + + + + + + + + + + diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000..d8ee5ee --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": ["jest", "node"] + }, + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] +} + + + + + + + + + + + + + + + +