From d5d4883917c304a3618ddee36c64b7b349b70a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C3=85=E2=80=9A=20Miczek?= Date: Fri, 13 Feb 2026 19:58:25 +0100 Subject: [PATCH] Docker, API proxy, Drone CI, .env Co-authored-by: Cursor --- .dockerignore | 10 + .drone.yml | 37 + .editorconfig | 32 + .env.example | 24 + .eslintrc.json | 60 + .gitignore | 63 + .prettierrc | 23 + Dockerfile | 43 + README.md | 93 + angular.json | 107 + bun.lock | 2471 +++++++++++++++++ docker-compose.yml | 10 + jest.config.js | 38 + package.json | 57 + postcss.config.js | 22 + scripts/build-with-env.js | 8 + scripts/serve-ssr.js | 24 + server.ts | 265 ++ setup-jest.ts | 17 + src/app/app.component.ts | 135 + src/app/app.config.ts | 14 + src/app/app.routes.ts | 33 + src/app/app.server.ts | 12 + src/app/core/interceptors/api.interceptor.ts | 57 + src/app/core/models/api-response.model.ts | 304 ++ src/app/core/models/gaming-goal.model.ts | 74 + src/app/core/models/sport-goal.model.ts | 41 + src/app/core/services/faceit.service.ts | 550 ++++ src/app/core/services/goals.service.ts | 359 +++ src/app/core/services/riot.service.ts | 946 +++++++ src/app/core/services/strava.service.ts | 398 +++ src/app/core/services/tracker-gg.service.ts | 145 + .../dashboard/dashboard.component.scss | 17 + .../features/dashboard/dashboard.component.ts | 560 ++++ .../gaming/gaming-goals.component.scss | 641 +++++ .../features/gaming/gaming-goals.component.ts | 1286 +++++++++ .../overall-progress-section.component.ts | 163 ++ .../features/sport/sport-goals.component.scss | 17 + .../features/sport/sport-goals.component.ts | 1159 ++++++++ .../sport-section/sport-section.component.ts | 110 + .../sport/strava-callback.component.ts | 91 + .../animated-counter.component.ts | 71 + .../basketball-game.component.ts | 524 ++++ .../chart-card/chart-card.component.ts | 145 + .../components/confetti/confetti.component.ts | 128 + .../custom-cursor/custom-cursor.component.ts | 239 ++ .../duck-shooting-game.component.ts | 506 ++++ .../components/footer/footer.component.ts | 114 + .../loading-screen.component.ts | 229 ++ .../magic-ball-game.component.ts | 327 +++ .../match-history/match-history.component.ts | 63 + .../components/navbar/navbar.component.ts | 174 ++ .../particle-background.component.ts | 468 ++++ .../progress-card/progress-card.component.ts | 232 ++ .../progress-ring/progress-ring.component.ts | 82 + .../space-invaders.component.ts | 367 +++ .../three-geometric.component.ts | 258 ++ .../three-trophy/three-trophy.component.ts | 241 ++ src/app/shared/utils/date.utils.ts | 83 + src/assets/gaming/logos/README.md | 13 + src/assets/gaming/logos/faceit.svg | 1 + src/assets/gaming/logos/lol.svg | 1 + src/assets/gaming/logos/rl.svg | 1 + src/assets/gaming/logos/tft.svg | 1 + src/environments/environment.prod.ts | 42 + src/environments/environment.ts | 40 + src/favicon.ico | 16 + src/index.html | 29 + src/main.server.ts | 17 + src/main.ts | 23 + src/styles.scss | 86 + tailwind.config.js | 26 + tsconfig.app.json | 27 + tsconfig.json | 29 + tsconfig.server.json | 27 + tsconfig.spec.json | 24 + 76 files changed, 15170 insertions(+) create mode 100644 .dockerignore create mode 100644 .drone.yml create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 angular.json create mode 100644 bun.lock create mode 100644 docker-compose.yml create mode 100644 jest.config.js create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 scripts/build-with-env.js create mode 100644 scripts/serve-ssr.js create mode 100644 server.ts create mode 100644 setup-jest.ts create mode 100644 src/app/app.component.ts create mode 100644 src/app/app.config.ts create mode 100644 src/app/app.routes.ts create mode 100644 src/app/app.server.ts create mode 100644 src/app/core/interceptors/api.interceptor.ts create mode 100644 src/app/core/models/api-response.model.ts create mode 100644 src/app/core/models/gaming-goal.model.ts create mode 100644 src/app/core/models/sport-goal.model.ts create mode 100644 src/app/core/services/faceit.service.ts create mode 100644 src/app/core/services/goals.service.ts create mode 100644 src/app/core/services/riot.service.ts create mode 100644 src/app/core/services/strava.service.ts create mode 100644 src/app/core/services/tracker-gg.service.ts create mode 100644 src/app/features/dashboard/dashboard.component.scss create mode 100644 src/app/features/dashboard/dashboard.component.ts create mode 100644 src/app/features/gaming/gaming-goals.component.scss create mode 100644 src/app/features/gaming/gaming-goals.component.ts create mode 100644 src/app/features/sport/overall-progress-section/overall-progress-section.component.ts create mode 100644 src/app/features/sport/sport-goals.component.scss create mode 100644 src/app/features/sport/sport-goals.component.ts create mode 100644 src/app/features/sport/sport-section/sport-section.component.ts create mode 100644 src/app/features/sport/strava-callback.component.ts create mode 100644 src/app/shared/components/animated-counter/animated-counter.component.ts create mode 100644 src/app/shared/components/basketball-game/basketball-game.component.ts create mode 100644 src/app/shared/components/chart-card/chart-card.component.ts create mode 100644 src/app/shared/components/confetti/confetti.component.ts create mode 100644 src/app/shared/components/custom-cursor/custom-cursor.component.ts create mode 100644 src/app/shared/components/duck-shooting-game/duck-shooting-game.component.ts create mode 100644 src/app/shared/components/footer/footer.component.ts create mode 100644 src/app/shared/components/loading-screen/loading-screen.component.ts create mode 100644 src/app/shared/components/magic-ball-game/magic-ball-game.component.ts create mode 100644 src/app/shared/components/match-history/match-history.component.ts create mode 100644 src/app/shared/components/navbar/navbar.component.ts create mode 100644 src/app/shared/components/particle-background/particle-background.component.ts create mode 100644 src/app/shared/components/progress-card/progress-card.component.ts create mode 100644 src/app/shared/components/progress-ring/progress-ring.component.ts create mode 100644 src/app/shared/components/space-invaders/space-invaders.component.ts create mode 100644 src/app/shared/components/three-geometric/three-geometric.component.ts create mode 100644 src/app/shared/components/three-trophy/three-trophy.component.ts create mode 100644 src/app/shared/utils/date.utils.ts create mode 100644 src/assets/gaming/logos/README.md create mode 100644 src/assets/gaming/logos/faceit.svg create mode 100644 src/assets/gaming/logos/lol.svg create mode 100644 src/assets/gaming/logos/rl.svg create mode 100644 src/assets/gaming/logos/tft.svg create mode 100644 src/environments/environment.prod.ts create mode 100644 src/environments/environment.ts create mode 100644 src/favicon.ico create mode 100644 src/index.html create mode 100644 src/main.server.ts create mode 100644 src/main.ts create mode 100644 src/styles.scss create mode 100644 tailwind.config.js create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.server.json create mode 100644 tsconfig.spec.json 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"] +} + + + + + + + + + + + + + + + +