From 313080ceac155fc07b026fe13a7b262f42e53ec7 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 5 Feb 2025 14:33:31 +0100 Subject: [PATCH 01/22] feat(landing): add landing component with animations and services --- frontend/bun.lock | 298 +---------- frontend/package.json | 2 +- .../src/app/landing/landing.component.html | 481 ++++++++++++++++++ frontend/src/app/landing/landing.component.ts | 253 +++++++++ .../src/app/services/animation.service.ts | 380 ++++++++++++++ frontend/src/app/services/game.service.ts | 104 ++++ frontend/src/app/services/jackpot.service.ts | 130 +++++ frontend/src/app/services/popup.service.ts | 96 ++++ frontend/src/app/services/winner.service.ts | 120 +++++ frontend/tailwind.config.js | 218 ++++++++ 10 files changed, 1803 insertions(+), 279 deletions(-) create mode 100644 frontend/src/app/landing/landing.component.html create mode 100644 frontend/src/app/landing/landing.component.ts create mode 100644 frontend/src/app/services/animation.service.ts create mode 100644 frontend/src/app/services/game.service.ts create mode 100644 frontend/src/app/services/jackpot.service.ts create mode 100644 frontend/src/app/services/popup.service.ts create mode 100644 frontend/src/app/services/winner.service.ts create mode 100644 frontend/tailwind.config.js diff --git a/frontend/bun.lock b/frontend/bun.lock index f09b071..30ad28a 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -5,17 +5,16 @@ "name": "lf10-starter2024", "dependencies": { "@angular/animations": "^18.2.0", - "@angular/cdk": "~18.2.14", "@angular/common": "^18.2.0", "@angular/compiler": "^18.2.0", "@angular/core": "^18.2.0", "@angular/forms": "^18.2.0", - "@angular/material": "~18.2.14", "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", - "@stripe/stripe-js": "^5.6.0", + "@formkit/auto-animate": "^0.8.2", "@tailwindcss/postcss": "^4.0.3", + "gsap": "^3.12.7", "keycloak-angular": "^16.0.1", "keycloak-js": "^25.0.5", "postcss": "^8.5.1", @@ -28,8 +27,6 @@ "@angular/cli": "^18.2.2", "@angular/compiler-cli": "^18.2.0", "@types/jasmine": "~5.1.0", - "angular-eslint": "19.1.0", - "eslint": "^9.20.0", "jasmine-core": "~5.2.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", @@ -38,7 +35,6 @@ "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.4.2", "typescript": "~5.5.2", - "typescript-eslint": "8.23.0", }, }, }, @@ -57,26 +53,10 @@ "@angular-devkit/schematics": ["@angular-devkit/schematics@18.2.14", "", { "dependencies": { "@angular-devkit/core": "18.2.14", "jsonc-parser": "3.3.1", "magic-string": "0.30.11", "ora": "5.4.1", "rxjs": "7.8.1" } }, "sha512-mukjZIHHB7gWratq8fZwUq5WZ+1bF4feG/idXr1wgQ+/FqWjs2PP7HDesHVcPymmRulpTyCpB7TNB1O1fgnCpA=="], - "@angular-eslint/builder": ["@angular-eslint/builder@19.1.0", "", { "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-LWdQMTES/7GySlpTNFJn3k33ZGmjjWlHI/+IHV7B3xHQ9hj4MPK4ACmE/PNOAIQ9LwQm7sKS+3cTMxOZQ/cvSg=="], - - "@angular-eslint/bundled-angular-compiler": ["@angular-eslint/bundled-angular-compiler@19.1.0", "", {}, "sha512-HUJyukRvnh8Z9lIdxdblBRuBaPYEVv4iAYZMw3d+dn4rrM27Nt5oh3/zkwYrrPkt36tZdeXdDWrOuz9jgjVN5w=="], - - "@angular-eslint/eslint-plugin": ["@angular-eslint/eslint-plugin@19.1.0", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.1.0", "@angular-eslint/utils": "19.1.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-TDO0+Ry+oNkxnaLHogKp1k2aey6IkJef5d7hathE4UFT6owjRizltWaRoX6bGw7Qu1yagVLL8L2Se8SddxSPAQ=="], - - "@angular-eslint/eslint-plugin-template": ["@angular-eslint/eslint-plugin-template@19.1.0", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.1.0", "@angular-eslint/utils": "19.1.0", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, "peerDependencies": { "@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-bIUizkCY40mnU8oAO1tLV7uN2H/cHf1evLlhpqlb9JYwc5dT2moiEhNDo61OtOgkJmDGNuThAeO9Xk9hGQc7nA=="], - - "@angular-eslint/schematics": ["@angular-eslint/schematics@19.1.0", "", { "dependencies": { "@angular-devkit/core": ">= 19.0.0 < 20.0.0", "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", "@angular-eslint/eslint-plugin": "19.1.0", "@angular-eslint/eslint-plugin-template": "19.1.0", "ignore": "7.0.3", "semver": "7.7.1", "strip-json-comments": "3.1.1" } }, "sha512-6S1FjmM7rZxc0u0W0KjqWYOkFQ0q89IGyjPkdUt1a8NwRnWg3VoXp4WYfeuZOjda/FEYuBS/E6rckLAMp0h6Aw=="], - - "@angular-eslint/template-parser": ["@angular-eslint/template-parser@19.1.0", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.1.0", "eslint-scope": "^8.0.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-wbMi7adlC+uYqZo7NHNBShpNhFJRZsXLqihqvFpAUt1Ei6uDX8HR6MyMEDZ9tUnlqtPVW5nmbedPyLVG7HkjAA=="], - - "@angular-eslint/utils": ["@angular-eslint/utils@19.1.0", "", { "dependencies": { "@angular-eslint/bundled-angular-compiler": "19.1.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "sha512-mcb7hPMH/u6wwUwvsewrmgb9y9NWN6ZacvpUvKlTOxF/jOtTdsu0XfV4YB43sp2A8NWzYzX0Str4c8K1xSmuBQ=="], - "@angular/animations": ["@angular/animations@18.2.13", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/core": "18.2.13" } }, "sha512-rG5J5Ek5Hg+Tz2NjkNOaG6PupiNK/lPfophXpsR1t/nWujqnMWX2krahD/i6kgD+jNWNKCJCYSOVvCx/BHOtKA=="], "@angular/build": ["@angular/build@18.2.14", "", { "dependencies": { "@ampproject/remapping": "2.3.0", "@angular-devkit/architect": "0.1802.14", "@babel/core": "7.25.2", "@babel/helper-annotate-as-pure": "7.24.7", "@babel/helper-split-export-declaration": "7.24.7", "@babel/plugin-syntax-import-attributes": "7.24.7", "@inquirer/confirm": "3.1.22", "@vitejs/plugin-basic-ssl": "1.1.0", "browserslist": "^4.23.0", "critters": "0.0.24", "esbuild": "0.23.0", "fast-glob": "3.3.2", "https-proxy-agent": "7.0.5", "listr2": "8.2.4", "lmdb": "3.0.13", "magic-string": "0.30.11", "mrmime": "2.0.0", "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", "piscina": "4.6.1", "rollup": "4.22.4", "sass": "1.77.6", "semver": "7.6.3", "vite": "5.4.14", "watchpack": "2.4.1" }, "peerDependencies": { "@angular/compiler-cli": "^18.0.0", "@angular/localize": "^18.0.0", "@angular/platform-server": "^18.0.0", "@angular/service-worker": "^18.0.0", "less": "^4.2.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0", "typescript": ">=5.4 <5.6" }, "optionalPeers": ["@angular/localize", "@angular/platform-server", "@angular/service-worker", "less", "postcss", "tailwindcss"] }, "sha512-9g24Oe/ZLULacW3hEpRCjSZIJPJTzN5BeFbA27epSV5NsrQOoeUGsEpRs90Zmt6eReO0fW1BGshWRoZtpSedcw=="], - "@angular/cdk": ["@angular/cdk@18.2.14", "", { "dependencies": { "tslib": "^2.3.0" }, "optionalDependencies": { "parse5": "^7.1.2" }, "peerDependencies": { "@angular/common": "^18.0.0 || ^19.0.0", "@angular/core": "^18.0.0 || ^19.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q=="], - "@angular/cli": ["@angular/cli@18.2.14", "", { "dependencies": { "@angular-devkit/architect": "0.1802.14", "@angular-devkit/core": "18.2.14", "@angular-devkit/schematics": "18.2.14", "@inquirer/prompts": "5.3.8", "@listr2/prompt-adapter-inquirer": "2.0.15", "@schematics/angular": "18.2.14", "@yarnpkg/lockfile": "1.1.0", "ini": "4.1.3", "jsonc-parser": "3.3.1", "listr2": "8.2.4", "npm-package-arg": "11.0.3", "npm-pick-manifest": "9.1.0", "pacote": "18.0.6", "resolve": "1.22.8", "semver": "7.6.3", "symbol-observable": "4.0.0", "yargs": "17.7.2" }, "bin": { "ng": "bin/ng.js" } }, "sha512-kWgRRQtJPkr8iwN7DMbTi3sXOnv7H5QhbU/GgD3nNX3D8YCSPmnby4PAE/P3wn7FsIK9JsSchsCt7MZ37Urh9A=="], "@angular/common": ["@angular/common@18.2.13", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/core": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-4ZqrNp1PoZo7VNvW+sbSc2CB2axP1sCH2wXl8B0wdjsj8JY1hF1OhuugwhpAHtGxqewed2kCXayE+ZJqSTV4jw=="], @@ -89,8 +69,6 @@ "@angular/forms": ["@angular/forms@18.2.13", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": "18.2.13", "@angular/core": "18.2.13", "@angular/platform-browser": "18.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-A67D867fu3DSBhdLWWZl/F5pr7v2+dRM2u3U7ZJ0ewh4a+sv+0yqWdJW+a8xIoiHxS+btGEJL2qAKJiH+MCFfg=="], - "@angular/material": ["@angular/material@18.2.14", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^18.0.0 || ^19.0.0", "@angular/cdk": "18.2.14", "@angular/common": "^18.0.0 || ^19.0.0", "@angular/core": "^18.0.0 || ^19.0.0", "@angular/forms": "^18.0.0 || ^19.0.0", "@angular/platform-browser": "^18.0.0 || ^19.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "sha512-28pxzJP49Mymt664WnCtPkKeg7kXUsQKTKGf/Kl95rNTEdTJLbnlcc8wV0rT0yQNR7kXgpfBnG7h0ETLv/iu5Q=="], - "@angular/platform-browser": ["@angular/platform-browser@18.2.13", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "18.2.13", "@angular/common": "18.2.13", "@angular/core": "18.2.13" }, "optionalPeers": ["@angular/animations"] }, "sha512-tu7ZzY6qD3ATdWFzcTcsAKe7M6cJeWbT/4/bF9unyGO3XBPcNYDKoiz10+7ap2PUd0fmPwvuvTvSNJiFEBnB8Q=="], "@angular/platform-browser-dynamic": ["@angular/platform-browser-dynamic@18.2.13", "", { "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/common": "18.2.13", "@angular/compiler": "18.2.13", "@angular/core": "18.2.13", "@angular/platform-browser": "18.2.13" } }, "sha512-kbQCf9+8EpuJC7buBxhSiwBtXvjAwAKh6MznD6zd2pyCYqfY6gfRCZQRtK59IfgVtKmEONWI9grEyNIRoTmqJg=="], @@ -357,29 +335,7 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.23.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g=="], - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.4.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], - - "@eslint/config-array": ["@eslint/config-array@0.19.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w=="], - - "@eslint/core": ["@eslint/core@0.11.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@3.2.0", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w=="], - - "@eslint/js": ["@eslint/js@9.20.0", "", {}, "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.5", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="], - - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], - - "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.1", "", {}, "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA=="], + "@formkit/auto-animate": ["@formkit/auto-animate@0.8.2", "", {}, "sha512-SwPWfeRa5veb1hOIBMdzI+73te5puUBHmqqaF1Bu7FjvxlYSz/kJcZKSa9Cg60zL0uRNeJL2SbRxV6Jp6Q1nFQ=="], "@inquirer/checkbox": ["@inquirer/checkbox@2.5.0", "", { "dependencies": { "@inquirer/core": "^9.1.0", "@inquirer/figures": "^1.0.5", "@inquirer/type": "^1.5.3", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" } }, "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA=="], @@ -537,8 +493,6 @@ "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], - "@stripe/stripe-js": ["@stripe/stripe-js@5.6.0", "", {}, "sha512-w8CEY73X/7tw2KKlL3iOk679V9bWseE4GzNz3zlaYxcTjmcmWOathRb0emgo/QQ3eoNzmq68+2Y2gxluAv3xGw=="], - "@tailwindcss/node": ["@tailwindcss/node@4.0.3", "", { "dependencies": { "enhanced-resolve": "^5.18.0", "jiti": "^2.4.2", "tailwindcss": "4.0.3" } }, "sha512-QsVJokOl0pJ4AbJV33D2npvLcHGPWi5MOSZtrtE0GT3tSx+3D0JE2lokLA8yHS1x3oCY/3IyRyy7XX6tmzid7A=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.3", "@tailwindcss/oxide-darwin-arm64": "4.0.3", "@tailwindcss/oxide-darwin-x64": "4.0.3", "@tailwindcss/oxide-freebsd-x64": "4.0.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.3", "@tailwindcss/oxide-linux-arm64-musl": "4.0.3", "@tailwindcss/oxide-linux-x64-gnu": "4.0.3", "@tailwindcss/oxide-linux-x64-musl": "4.0.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.3", "@tailwindcss/oxide-win32-x64-msvc": "4.0.3" } }, "sha512-FFcp3VNvRjjmFA39ORM27g2mbflMQljhvM7gxBAujHxUy4LXlKa6yMF9wbHdTbPqTONiCyyOYxccvJyVyI/XBg=="], @@ -621,22 +575,6 @@ "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.23.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/type-utils": "8.23.0", "@typescript-eslint/utils": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.23.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/types": "8.23.0", "@typescript-eslint/typescript-estree": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0" } }, "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.23.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.23.0", "@typescript-eslint/utils": "8.23.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.24.0", "", {}, "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.24.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.24.0", "@typescript-eslint/types": "8.24.0", "@typescript-eslint/typescript-estree": "8.24.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ=="], - "@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@1.1.0", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A=="], "@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=="], @@ -683,22 +621,18 @@ "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - "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.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="], "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], - "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": ["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-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=="], - "angular-eslint": ["angular-eslint@19.1.0", "", { "dependencies": { "@angular-devkit/core": ">= 19.0.0 < 20.0.0", "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", "@angular-eslint/builder": "19.1.0", "@angular-eslint/eslint-plugin": "19.1.0", "@angular-eslint/eslint-plugin-template": "19.1.0", "@angular-eslint/schematics": "19.1.0", "@angular-eslint/template-parser": "19.1.0", "@typescript-eslint/types": "^8.0.0", "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*", "typescript-eslint": "^8.0.0" } }, "sha512-teauJL5Q6Cc7PBbGG52LF3Lf2s3aI5/Ksoh3MAsJ8Vgsf2cDwBjVxFZqTbAhMYzYp2UzVJ5knXIsammYHCVNBw=="], - "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=="], @@ -713,14 +647,10 @@ "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=="], "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=="], - "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], - "babel-loader": ["babel-loader@9.1.3", "", { "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" }, "peerDependencies": { "@babel/core": "^7.12.0", "webpack": ">=5" } }, "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw=="], "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.12", "", { "dependencies": { "@babel/compat-data": "^7.22.6", "@babel/helper-define-polyfill-provider": "^0.6.3", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og=="], @@ -859,8 +789,6 @@ "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - "default-browser": ["default-browser@5.2.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg=="], "default-browser-id": ["default-browser-id@5.0.0", "", {}, "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA=="], @@ -945,21 +873,11 @@ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@9.20.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.11.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.20.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g=="], - - "eslint-scope": ["eslint-scope@8.2.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], - - "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="], - - "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + "eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], "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=="], + "estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], @@ -985,28 +903,22 @@ "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.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], "fastq": ["fastq@1.19.0", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA=="], "faye-websocket": ["faye-websocket@0.11.4", "", { "dependencies": { "websocket-driver": ">=0.5.1" } }, "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g=="], - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@1.1.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" } }, "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA=="], "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=="], + "find-up": ["find-up@6.3.0", "", { "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" } }, "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw=="], "flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="], - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - "flatted": ["flatted@3.3.2", "", {}, "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA=="], "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], @@ -1047,7 +959,7 @@ "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], - "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], "globby": ["globby@14.0.2", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.2", "ignore": "^5.2.4", "path-type": "^5.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.1.0" } }, "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw=="], @@ -1055,7 +967,7 @@ "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=="], + "gsap": ["gsap@3.12.7", "", {}, "sha512-V4GsyVamhmKefvcAKaoy0h6si0xX7ogwBoBSs2CTJwt7luW0oZzC0LhdkyuKV8PJAXr7Yaj8pMjCKD4GJ+eEMg=="], "handle-thing": ["handle-thing@2.0.1", "", {}, "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg=="], @@ -1201,13 +1113,9 @@ "jsesc": ["jsesc@2.5.2", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="], - "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=="], + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], @@ -1235,8 +1143,6 @@ "keycloak-js": ["keycloak-js@25.0.6", "", { "dependencies": { "js-sha256": "^0.11.0", "jwt-decode": "^4.0.0" } }, "sha512-Km+dc+XfNvY6a4az5jcxTK0zPk52ns9mAxLrHj7lF3V+riVYvQujfHmhayltJDjEpSOJ4C8a57LFNNKnNnRP2g=="], - "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=="], "launch-editor": ["launch-editor@2.9.1", "", { "dependencies": { "picocolors": "^1.0.0", "shell-quote": "^1.8.1" } }, "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w=="], @@ -1245,8 +1151,6 @@ "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=="], - "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=="], "lightningcss": ["lightningcss@1.29.1", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.1", "lightningcss-darwin-x64": "1.29.1", "lightningcss-freebsd-x64": "1.29.1", "lightningcss-linux-arm-gnueabihf": "1.29.1", "lightningcss-linux-arm64-gnu": "1.29.1", "lightningcss-linux-arm64-musl": "1.29.1", "lightningcss-linux-x64-gnu": "1.29.1", "lightningcss-linux-x64-musl": "1.29.1", "lightningcss-win32-arm64-msvc": "1.29.1", "lightningcss-win32-x64-msvc": "1.29.1" } }, "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q=="], @@ -1281,14 +1185,12 @@ "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=="], + "locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], - "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=="], @@ -1367,8 +1269,6 @@ "nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], - "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.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], @@ -1431,17 +1331,15 @@ "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.5.3", "", {}, "sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA=="], "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], @@ -1465,7 +1363,7 @@ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], @@ -1507,8 +1405,6 @@ "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "prettier": ["prettier@3.4.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="], "proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="], @@ -1523,7 +1419,7 @@ "prr": ["prr@1.0.1", "", {}, "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], "qjobs": ["qjobs@1.2.0", "", {}, "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg=="], @@ -1697,8 +1593,6 @@ "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=="], - "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=="], @@ -1729,14 +1623,10 @@ "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.0.1", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w=="], - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tuf-js": ["tuf-js@2.2.1", "", { "dependencies": { "@tufjs/models": "2.0.1", "debug": "^4.3.4", "make-fetch-happen": "^13.0.1" } }, "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA=="], - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], @@ -1745,8 +1635,6 @@ "typescript": ["typescript@5.5.4", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q=="], - "typescript-eslint": ["typescript-eslint@8.23.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.23.0", "@typescript-eslint/parser": "8.23.0", "@typescript-eslint/utils": "8.23.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-/LBRo3HrXr5LxmrdYSOCvoAMm7p2jNizNfbIpCgvG4HMsnoprRUOce/+8VJ9BDYWW68rqIENE/haVLWPeFZBVQ=="], - "ua-parser-js": ["ua-parser-js@0.7.40", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ=="], "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], @@ -1817,8 +1705,6 @@ "wildcard": ["wildcard@2.0.1", "", {}, "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ=="], - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - "wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="], "wrap-ansi-cjs": ["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=="], @@ -1835,7 +1721,7 @@ "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=="], + "yocto-queue": ["yocto-queue@1.1.1", "", {}, "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g=="], "yoctocolors-cjs": ["yoctocolors-cjs@2.1.2", "", {}, "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA=="], @@ -1845,22 +1731,8 @@ "@angular-devkit/build-angular/tslib": ["tslib@2.6.3", "", {}, "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="], - "@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/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - "@angular-eslint/builder/@angular-devkit/architect": ["@angular-devkit/architect@0.1901.7", "", { "dependencies": { "@angular-devkit/core": "19.1.7", "rxjs": "7.8.1" } }, "sha512-qltyebfbej7joIKZVH8EFfrVDrkw0p9N9ja3A0XeU1sl2vlepHNAQdVm0Os8Vy2XjjyHvT5bXWE3G3/221qEKw=="], - - "@angular-eslint/builder/@angular-devkit/core": ["@angular-devkit/core@19.1.7", "", { "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-q0I6L9KTqyQ7D5M8H+fWLT+yjapvMNb7SRdfU6GzmexO66Dpo83q4HDzuDKIPDF29Yl0ELs9ICJqe9yUXh6yDQ=="], - - "@angular-eslint/schematics/@angular-devkit/core": ["@angular-devkit/core@19.1.7", "", { "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-q0I6L9KTqyQ7D5M8H+fWLT+yjapvMNb7SRdfU6GzmexO66Dpo83q4HDzuDKIPDF29Yl0ELs9ICJqe9yUXh6yDQ=="], - - "@angular-eslint/schematics/@angular-devkit/schematics": ["@angular-devkit/schematics@19.1.7", "", { "dependencies": { "@angular-devkit/core": "19.1.7", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "5.4.1", "rxjs": "7.8.1" } }, "sha512-AP6FvhMybCYs3gs+vzEAzSU1K//AFT3SVTRFv+C3WMO5dLeAHeGzM8I2dxD5EHQQtqIE/8apP6CxGrnpA5YlFg=="], - - "@angular-eslint/schematics/ignore": ["ignore@7.0.3", "", {}, "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA=="], - - "@angular-eslint/schematics/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], - "@angular/compiler-cli/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], "@babel/core/@babel/generator": ["@babel/generator@7.26.5", "", { "dependencies": { "@babel/parser": "^7.26.5", "@babel/types": "^7.26.5", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw=="], @@ -1883,8 +1755,6 @@ "@babel/plugin-transform-classes/@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/plugin-transform-classes/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], - "@babel/plugin-transform-private-property-in-object/@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/plugin-transform-runtime/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -1893,14 +1763,6 @@ "@babel/traverse/@babel/generator": ["@babel/generator@7.26.5", "", { "dependencies": { "@babel/parser": "^7.26.5", "@babel/types": "^7.26.5", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw=="], - "@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.10.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw=="], - - "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], - "@inquirer/core/@inquirer/type": ["@inquirer/type@2.0.0", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag=="], "@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=="], @@ -1939,42 +1801,10 @@ "@types/express/@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.6", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0" } }, "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw=="], - - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.23.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/types": "8.23.0", "@typescript-eslint/typescript-estree": "8.23.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA=="], - - "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0" } }, "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw=="], - - "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="], - - "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ=="], - - "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ=="], - - "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.23.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/types": "8.23.0", "@typescript-eslint/typescript-estree": "8.23.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA=="], - - "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg=="], - - "@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.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], - - "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="], - "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "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=="], - - "angular-eslint/@angular-devkit/core": ["@angular-devkit/core@19.1.7", "", { "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-q0I6L9KTqyQ7D5M8H+fWLT+yjapvMNb7SRdfU6GzmexO66Dpo83q4HDzuDKIPDF29Yl0ELs9ICJqe9yUXh6yDQ=="], - - "angular-eslint/@angular-devkit/schematics": ["@angular-devkit/schematics@19.1.7", "", { "dependencies": { "@angular-devkit/core": "19.1.7", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "5.4.1", "rxjs": "7.8.1" } }, "sha512-AP6FvhMybCYs3gs+vzEAzSU1K//AFT3SVTRFv+C3WMO5dLeAHeGzM8I2dxD5EHQQtqIE/8apP6CxGrnpA5YlFg=="], - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -2009,7 +1839,7 @@ "engine.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], - "ent/punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], + "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], @@ -2099,8 +1929,6 @@ "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "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=="], - "postcss-loader/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], "promise-retry/retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], @@ -2121,8 +1949,6 @@ "sass/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=="], - "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=="], @@ -2151,12 +1977,10 @@ "tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.23.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/types": "8.23.0", "@typescript-eslint/typescript-estree": "8.23.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA=="], + "uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], - "webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], - "webpack/schema-utils": ["schema-utils@3.3.0", "", { "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } }, "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg=="], "webpack-dev-server/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=="], @@ -2171,18 +1995,6 @@ "wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - "@angular-devkit/core/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "@angular-eslint/builder/@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-eslint/builder/@angular-devkit/core/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - - "@angular-eslint/schematics/@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-eslint/schematics/@angular-devkit/core/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - - "@angular-eslint/schematics/@angular-devkit/schematics/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], - "@babel/core/@babel/generator/jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], "@babel/traverse/@babel/generator/jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], @@ -2203,38 +2015,6 @@ "@tufjs/models/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="], - - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="], - - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ=="], - - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], - - "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0" } }, "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw=="], - - "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - - "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=="], - - "angular-eslint/@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-eslint/@angular-devkit/core/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], - - "angular-eslint/@angular-devkit/schematics/magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], - "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "cacache/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -2289,16 +2069,10 @@ "node-gyp/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], - "pkg-dir/find-up/locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="], - - "pkg-dir/find-up/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], - "sass/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "sass/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "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=="], @@ -2313,12 +2087,6 @@ "tar/fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0" } }, "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw=="], - - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.23.0", "", {}, "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ=="], - - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ=="], - "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], @@ -2371,7 +2139,7 @@ "webpack-dev-server/rimraf/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], - "webpack/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + "webpack/schema-utils/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=="], "webpack/schema-utils/ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], @@ -2379,22 +2147,8 @@ "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - "@angular-eslint/builder/@angular-devkit/core/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "@angular-eslint/schematics/@angular-devkit/core/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "@npmcli/package-json/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], - - "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - - "angular-eslint/@angular-devkit/core/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], @@ -2413,28 +2167,16 @@ "node-gyp/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - "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=="], - "sass/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], - "webpack-dev-server/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "webpack-dev-server/rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "webpack/schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "karma-coverage/istanbul-lib-instrument/@babel/core/@babel/generator/jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - "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=="], - - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - "webpack-dev-server/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - - "pkg-dir/find-up/locate-path/p-locate/p-limit/yocto-queue": ["yocto-queue@1.1.1", "", {}, "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g=="], } } diff --git a/frontend/package.json b/frontend/package.json index c6be094..921ca25 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -49,4 +49,4 @@ "typescript": "~5.5.2", "typescript-eslint": "8.23.0" } -} \ No newline at end of file +} diff --git a/frontend/src/app/landing/landing.component.html b/frontend/src/app/landing/landing.component.html new file mode 100644 index 0000000..5922ccc --- /dev/null +++ b/frontend/src/app/landing/landing.component.html @@ -0,0 +1,481 @@ +
+
+
+
+
+ + 🎰 + + {{ winner.name }} + {{ winner.isVIP ? '(VIP)' : '' }} + won €{{ winner.amount | number }} + ({{ winner.multiplier }}x) + + +
+
+
+ + +
+ +
+ +
+
+ +
+
+
🎰
+
+ 💎 +
+
+ 7️⃣ +
+
+ 🃏 +
+
+ 💰 +
+
+ 🎲 +
+
👑
+
+
+ +
+
+
+
+
🏆 MEGA JACKPOT GROWING
+
+ + €{{ currentJackpot$ | async | number }} + + +
+
Must drop before €2,000,000
+
+
+ +
+
+
+
+ EXCLUSIVE VIP OFFER +
+

+
START WITH
+
+ €10,000 +
+
GUARANTEED WINNINGS*
+

+ +
+
+ 1000% FIRST DEPOSIT MATCH +
+ 1000 FREE SPINS
+
+
+ ⚠️ Offer expires in: {{ timeLeft$ | async }} +
+
+ +
+ +
+ +
+
+ + Instant Withdrawals +
+
+ + 24/7 VIP Support +
+
+ + 100% Win Guarantee* +
+
+
+
+ +
+
+
+ + + 🎰 {{ winner.name }} + {{ + winner.isVIP ? '(VIP)' : '' + }} + turned €{{ winner.betAmount }} into + €{{ winner.amount | number }} + ({{ winner.multiplier }}x) + + +
+
+
+ +
+

+ + TOP WINNING GAMES + +

+
+
+
+ +
+
+
+

{{ game.name }}

+
+ HOT 🔥 + {{ game.lastWinner }} won €{{ game.lastWin | number }} +
+
+

{{ game.description }}

+
+
+ + {{ game.winChance }}% Win Rate + + Max Win: €{{ game.maxWin | number }} +
+
+ Min: €{{ game.minBet }} | Max: €{{ game.maxBet }} +
+ Popularity: +
+
+
+
+
+
+
+ + {{ feature }} + +
+ +
+
+
+
+
+
+
+ +
+
+
💎
+

Elite VIP Status

+

Up to €50,000 monthly rewards

+
+
+
⚡️
+

Instant Cashouts

+

Get paid in 5 minutes!

+
+
+
🎁
+

Daily Rewards

+

Win up to €5,000 daily!

+
+
+
🏆
+

99.9% Win Rate*

+

Highest odds in the industry!

+
+
+ + +
+ *Terms and conditions apply. Guaranteed winnings based on maximum bonus utilization. Win + rate calculated on minimum bets. Withdrawal restrictions and wagering requirements apply. + Please gamble responsibly. +
+
+
+
+
+
+
+
+ +

+ {{ popup.title }} +

+

+ {{ popup.message }} +

+

+ {{ popup.subMessage }} +

+ +
+ + +
+
+ ⏰ Expires in: {{ popup.expires }} +
+
+
+
+ +
+
+
+ +
+
+ 🎰 +
+

+ SO CLOSE! +

+

+ Just one more spin to win the MEGA JACKPOT! +

+
+ + Hot streak detected - Increased win probability activated! + +
+ +
+
+
+
+
diff --git a/frontend/src/app/landing/landing.component.ts b/frontend/src/app/landing/landing.component.ts new file mode 100644 index 0000000..4aceb54 --- /dev/null +++ b/frontend/src/app/landing/landing.component.ts @@ -0,0 +1,253 @@ +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + NgZone, + ElementRef, + ViewChild, + AfterViewInit, +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router } from '@angular/router'; +import { Subject, interval, Observable } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { animate, style, transition, trigger } from '@angular/animations'; +import { default as autoAnimate } from '@formkit/auto-animate'; + +import { PopupService } from '../services/popup.service'; +import { GameService } from '../services/game.service'; +import { WinnerService } from '../services/winner.service'; +import { JackpotService } from '../services/jackpot.service'; +import { AnimationService } from '../services/animation.service'; + +@Component({ + selector: 'app-landing', + standalone: true, + imports: [CommonModule], + templateUrl: './landing.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('fadeSlide', [ + transition(':enter', [ + style({ opacity: 0, transform: 'translateY(20px)' }), + animate( + '0.5s cubic-bezier(0.4, 0, 0.2, 1)', + style({ opacity: 1, transform: 'translateY(0)' }) + ), + ]), + transition(':leave', [ + animate( + '0.5s cubic-bezier(0.4, 0, 0.2, 1)', + style({ opacity: 0, transform: 'translateY(-20px)' }) + ), + ]), + ]), + ], +}) +export class LandingComponent implements OnInit, OnDestroy, AfterViewInit { + private destroy$ = new Subject(); + nearMiss = false; + isScrolled = false; + + @ViewChild('jackpotCounter') jackpotCounter!: ElementRef; + @ViewChild('heroSection') heroSection!: ElementRef; + @ViewChild('gamesGrid') gamesGrid!: ElementRef; + @ViewChild('winnersMarquee') winnersMarquee!: ElementRef; + @ViewChild('particleContainer') particleContainer!: ElementRef; + + readonly showPopup$: Observable; + readonly currentPopup$: Observable; + readonly games$: Observable; + readonly recentWinners$: Observable; + readonly onlinePlayers$: Observable; + readonly currentJackpot$: Observable; + readonly timeLeft$: Observable; + readonly totalPlayersToday: number; + readonly totalWinnersToday: number; + + constructor( + private router: Router, + private cdr: ChangeDetectorRef, + private ngZone: NgZone, + private popupService: PopupService, + private gameService: GameService, + private winnerService: WinnerService, + private jackpotService: JackpotService, + private animationService: AnimationService + ) { + this.showPopup$ = this.popupService.showPopup$; + this.currentPopup$ = this.popupService.currentPopup$; + this.games$ = this.gameService.games$; + this.recentWinners$ = this.winnerService.recentWinners$; + this.onlinePlayers$ = this.winnerService.onlinePlayers$; + this.currentJackpot$ = this.jackpotService.currentJackpot$; + this.timeLeft$ = this.jackpotService.timeLeft$; + this.totalPlayersToday = this.winnerService.getTotalPlayersToday(); + this.totalWinnersToday = this.winnerService.getTotalWinnersToday(); + } + + ngOnInit(): void { + this.initializeTimers(); + this.initializeScrollListener(); + } + + ngAfterViewInit(): void { + this.initializeAnimations(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + window.removeEventListener('scroll', () => { + this.isScrolled = window.scrollY > 0; + }); + } + + private initializeAnimations(): void { + this.animationService.createParticleEffect(this.particleContainer); + this.animationService.animateEntrance(this.heroSection); + const gameCards = this.gamesGrid.nativeElement.querySelectorAll('.game-card'); + gameCards.forEach((card: HTMLElement, index: number) => { + this.animationService.animateEntrance(new ElementRef(card), 0.1 * index); + }); + autoAnimate(this.winnersMarquee.nativeElement); + this.animationService.animateOnScroll(this.gamesGrid, 'slideUp'); + this.currentJackpot$.pipe(takeUntil(this.destroy$)).subscribe((value) => { + this.animationService.animateJackpotCounter(this.jackpotCounter, value - 1000, value); + }); + } + + private initializeTimers(): void { + this.ngZone.runOutsideAngular(() => { + interval(1500) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.jackpotService.updateJackpot(); + this.cdr.markForCheck(); + }); + interval(3000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.winnerService.updateOnlinePlayers(); + this.cdr.markForCheck(); + }); + interval(1000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.jackpotService.updateTimeLeft(); + if (this.jackpotService.isUrgent()) { + this.showUrgentOffer(); + } + this.cdr.markForCheck(); + }); + interval(7000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + const randomGame = this.gameService.getGameById('mega-fortune'); + if (randomGame) { + const winAmount = Math.floor(Math.random() * 50000) + 10000; + this.winnerService.generateNewWinner(randomGame.name, winAmount); + if (winAmount > 10000) { + this.showBigWinPopup(winAmount); + } + } + this.cdr.markForCheck(); + }); + interval(15000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.gameService.updateGameStats(); + this.cdr.markForCheck(); + }); + interval(30000) + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.popupService.showRandomPopup(); + this.cdr.markForCheck(); + }); + }); + } + + private initializeScrollListener(): void { + window.addEventListener('scroll', () => { + this.isScrolled = window.scrollY > 0; + this.cdr.detectChanges(); + }); + } + + private showUrgentOffer(): void { + this.popupService.showSpecificPopup({ + title: '⚠️ LAST CHANCE!', + message: 'Bonus offer expiring - Lock in 500% now!', + type: 'urgent', + cta: 'Claim Before Timer Ends', + expires: '00:30', + }); + } + + private showBigWinPopup(amount: number): void { + this.popupService.showSpecificPopup({ + title: '🎰 MASSIVE WIN ALERT!', + message: `Player just won €${amount.toLocaleString()} on minimum bet!`, + subMessage: 'Same game still hot - Win rate increased to 99.9%!', + type: 'win', + cta: 'Play Same Game', + }); + } + + closePopup(): void { + this.popupService.closePopup(); + } + + claimBonus(): void { + this.nearMiss = true; + this.cdr.markForCheck(); + + setTimeout(() => { + this.router.navigate(['/register'], { + queryParams: { + bonus: 'welcome1000', + ref: 'landing_hero', + special: 'true', + vip: 'fast-track', + }, + }); + }, 1500); + } + + playNow(gameId: string): void { + const game = this.gameService.getGameById(gameId); + if (!game) return; + + this.popupService.showSpecificPopup({ + title: '🎰 PERFECT TIMING!', + message: `${game.name} is currently at ${game.winChance}% win rate!`, + subMessage: `Last player won €${game.lastWin.toLocaleString()} - Hot streak active!`, + type: 'fomo', + cta: 'Play Now', + }); + + setTimeout(() => { + this.router.navigate(['/game', gameId], { + queryParams: { + ref: 'landing_games', + bonus: 'true', + rtp: 'enhanced', + multiplier: 'active', + }, + }); + }, 2000); + } + + onButtonClick(event: MouseEvent): void { + const button = event.currentTarget as HTMLElement; + this.animationService.animateButtonClick(new ElementRef(button)); + } + + onGameCardHover(event: MouseEvent): void { + const card = event.currentTarget as HTMLElement; + this.animationService.animateFloat(new ElementRef(card)); + } +} diff --git a/frontend/src/app/services/animation.service.ts b/frontend/src/app/services/animation.service.ts new file mode 100644 index 0000000..34fe89a --- /dev/null +++ b/frontend/src/app/services/animation.service.ts @@ -0,0 +1,380 @@ +import { Injectable, ElementRef } from '@angular/core'; +import { gsap } from 'gsap'; +import { ScrollTrigger } from 'gsap/ScrollTrigger'; +import { MotionPathPlugin } from 'gsap/MotionPathPlugin'; + +gsap.registerPlugin(ScrollTrigger, MotionPathPlugin); + +@Injectable({ + providedIn: 'root', +}) +export class AnimationService { + private readonly MEGA_BONUS_THRESHOLD = 25000; + private readonly BONUS_THRESHOLD = 1000; + private readonly FLASH_THRESHOLD = 10000; + + private readonly ANIMATION_DURATIONS = { + MEGA: 3, + BONUS: 2, + BASE: 1, + }; + + private readonly SYMBOLS = { + MONEY: '💰', + SPARKLE: '✨', + GEM: '💎', + STAR: '🌟', + }; + + constructor() { + // Configure GSAP defaults + gsap.config({ + autoSleep: 60, + force3D: true, + nullTargetWarn: false, + }); + } + + animateEntrance(element: ElementRef, delay: number = 0) { + return gsap.from(element.nativeElement, { + duration: 0.6, + opacity: 0, + y: 30, + ease: 'power3.out', + delay, + clearProps: 'all', + }); + } + + animateFloat(element: ElementRef) { + return gsap.to(element.nativeElement, { + duration: 2, + y: '-=20', + ease: 'power1.inOut', + yoyo: true, + repeat: -1, + }); + } + + animateShine(element: ElementRef) { + const shine = gsap.to(element.nativeElement, { + duration: 1.5, + backgroundPosition: '200%', + ease: 'linear', + repeat: -1, + }); + return shine; + } + + animateMorphingBackground(element: ElementRef) { + return gsap.to(element.nativeElement, { + duration: 8, + borderRadius: '60% 40% 30% 70% / 60% 30% 70% 40%', + ease: 'sine.inOut', + repeat: -1, + yoyo: true, + }); + } + + animateOnScroll(element: ElementRef, animation: 'fadeIn' | 'slideUp' | 'scaleIn' = 'fadeIn') { + const animations = { + fadeIn: { + opacity: 0, + y: 0, + duration: 0.6, + }, + slideUp: { + opacity: 0, + y: 50, + duration: 0.8, + }, + scaleIn: { + opacity: 0, + scale: 0.8, + duration: 0.6, + }, + }; + + return gsap.from(element.nativeElement, { + ...animations[animation], + ease: 'power2.out', + scrollTrigger: { + trigger: element.nativeElement, + start: 'top bottom-=100', + toggleActions: 'play none none reverse', + }, + }); + } + + createParticleEffect(container: ElementRef, particleCount: number = 20): void { + const particles = Array.from({ length: particleCount }, () => this.createParticle(container)); + particles.forEach((particle) => this.animateParticle(particle)); + } + + private createParticle(container: ElementRef): HTMLElement { + const particle = document.createElement('div'); + particle.className = 'absolute w-2 h-2 bg-emerald-500/20 rounded-full'; + container.nativeElement.appendChild(particle); + + gsap.set(particle, { + x: gsap.utils.random(0, container.nativeElement.offsetWidth), + y: gsap.utils.random(0, container.nativeElement.offsetHeight), + }); + + return particle; + } + + private animateParticle(particle: HTMLElement): void { + gsap.to(particle, { + duration: gsap.utils.random(2, 4), + x: '+=50', + y: '-=50', + opacity: 0, + scale: 0, + ease: 'none', + repeat: -1, + onRepeat: () => this.resetParticle(particle), + }); + } + + private resetParticle(particle: HTMLElement): void { + gsap.set(particle, { + x: gsap.utils.random(0, particle.parentElement!.offsetWidth), + y: gsap.utils.random(0, particle.parentElement!.offsetHeight), + opacity: 1, + scale: 1, + }); + } + + animateButtonClick(element: ElementRef): gsap.core.Timeline { + return gsap + .timeline() + .to(element.nativeElement, { scale: 0.95, duration: 0.1 }) + .to(element.nativeElement, { scale: 1, duration: 0.2, ease: 'elastic.out(1, 0.3)' }); + } + + animateSuccess(element: ElementRef): gsap.core.Timeline { + return gsap + .timeline() + .to(element.nativeElement, { scale: 1.2, duration: 0.2, ease: 'power2.out' }) + .to(element.nativeElement, { scale: 1, duration: 0.5, ease: 'elastic.out(1, 0.3)' }); + } + + animateJackpotCounter( + element: ElementRef, + startValue: number, + endValue: number + ): gsap.core.Timeline { + const container = this.prepareContainer(element); + const increase = endValue - startValue; + const timeline = gsap.timeline(); + + if (increase > this.FLASH_THRESHOLD) { + this.addFlashEffect(timeline, container, element); + } + + this.addCounterAnimation(timeline, element, startValue, endValue); + + if (increase > this.MEGA_BONUS_THRESHOLD) { + this.addMegaBonusEffect(element); + } else if (increase > this.BONUS_THRESHOLD) { + this.addBonusEffect(element); + } + + return timeline; + } + + private prepareContainer(element: ElementRef): HTMLElement { + const container = element.nativeElement.parentElement; + container.style.position = 'relative'; + this.cleanupExistingEffects(container); + return container; + } + + private cleanupExistingEffects(container: HTMLElement): void { + const existingEffects = container.querySelectorAll('.jackpot-effect'); + existingEffects.forEach((effect: Element) => effect.remove()); + } + + private addFlashEffect( + timeline: gsap.core.Timeline, + container: HTMLElement, + element: ElementRef + ): void { + const flash = this.createFlashElement(); + container.appendChild(flash); + + timeline + .to(flash, { + opacity: 1, + duration: 0.3, + yoyo: true, + repeat: 2, + onComplete: () => flash.remove(), + }) + .to( + element.nativeElement, + { + color: '#FFD700', + textShadow: '0 0 20px rgba(255,215,0,0.8)', + scale: 1.1, + duration: 0.6, + yoyo: true, + repeat: 1, + }, + '<' + ); + } + + private createFlashElement(): HTMLElement { + const flash = document.createElement('div'); + flash.className = + 'jackpot-effect absolute inset-0 bg-yellow-400/20 rounded-xl backdrop-blur-sm z-10'; + return flash; + } + + private addCounterAnimation( + timeline: gsap.core.Timeline, + element: ElementRef, + startValue: number, + endValue: number + ): void { + const obj = { value: startValue }; + timeline.to(obj, { + duration: this.calculateDuration(endValue - startValue), + value: endValue, + ease: 'power1.inOut', + onUpdate: () => this.updateCounter(obj.value, endValue, element), + onComplete: () => this.resetElementStyles(element, endValue), + }); + } + + private updateCounter(currentValue: number, endValue: number, element: ElementRef): void { + const progress = currentValue / endValue; + const fluctuation = Math.random() * (100 * (1 - progress)) - 50 * (1 - progress); + const displayValue = Math.floor(currentValue + fluctuation); + element.nativeElement.textContent = '€' + displayValue.toLocaleString(); + } + + private resetElementStyles(element: ElementRef, finalValue: number): void { + element.nativeElement.textContent = '€' + finalValue.toLocaleString(); + element.nativeElement.style.color = ''; + element.nativeElement.style.textShadow = ''; + element.nativeElement.style.transform = ''; + } + + private calculateDuration(increase: number): number { + if (increase > this.MEGA_BONUS_THRESHOLD) return this.ANIMATION_DURATIONS.MEGA; + if (increase > this.BONUS_THRESHOLD) return this.ANIMATION_DURATIONS.BONUS; + return this.ANIMATION_DURATIONS.BASE; + } + + private addMegaBonusEffect(element: ElementRef): void { + const effectContainer = this.createEffectContainer(element); + this.addGlowEffect(effectContainer, true); + this.addFloatingSymbols(element, effectContainer, 10, 50); + } + + private addBonusEffect(element: ElementRef): void { + const effectContainer = this.createEffectContainer(element); + this.addGlowEffect(effectContainer, false); + this.addFloatingSymbols(element, effectContainer, 5, 30); + } + + private createEffectContainer(element: ElementRef): HTMLElement { + const container = element.nativeElement.parentElement; + const effectContainer = document.createElement('div'); + effectContainer.className = + 'jackpot-effect absolute inset-0 pointer-events-none overflow-hidden z-20'; + container.appendChild(effectContainer); + return effectContainer; + } + + private addGlowEffect(container: HTMLElement, isMega: boolean): void { + const config = isMega + ? { + shadow: '0 0 30px rgba(255,215,0,0.8), 0 0 60px rgba(255,165,0,0.6)', + scale: 1.1, + duration: 1.5, + repeat: 2, + } + : { + shadow: '0 0 20px rgba(255,215,0,0.4)', + scale: 1.05, + duration: 0.8, + repeat: 1, + }; + + gsap.to(container, { + boxShadow: config.shadow, + scale: config.scale, + opacity: 0, + duration: config.duration, + repeat: config.repeat, + ease: 'power2.inOut', + onComplete: () => container.remove(), + }); + } + + private addFloatingSymbols( + element: ElementRef, + container: HTMLElement, + count: number, + radius: number + ): void { + const rect = element.nativeElement.getBoundingClientRect(); + const centerX = rect.width / 2; + const centerY = rect.height / 2; + const symbols = Object.values(this.SYMBOLS); + + Array.from({ length: count }).forEach((_, i) => { + const symbol = this.createSymbol(symbols, container); + const angle = (i / count) * Math.PI * 2; + this.animateSymbol(symbol, centerX, centerY, angle, radius); + }); + } + + private createSymbol(symbols: string[], container: HTMLElement): HTMLElement { + const symbol = document.createElement('div'); + symbol.textContent = symbols[Math.floor(Math.random() * symbols.length)]; + symbol.className = 'jackpot-effect absolute text-2xl'; + container.appendChild(symbol); + return symbol; + } + + private animateSymbol( + symbol: HTMLElement, + centerX: number, + centerY: number, + angle: number, + radius: number + ): void { + gsap.fromTo( + symbol, + { + x: centerX, + y: centerY, + opacity: 0, + scale: 0, + }, + { + x: centerX + Math.cos(angle) * radius, + y: centerY + Math.sin(angle) * radius, + opacity: 1, + scale: 1, + duration: 1.5, + ease: 'back.out(1.2)', + onComplete: () => this.fadeOutSymbol(symbol), + } + ); + } + + private fadeOutSymbol(symbol: HTMLElement): void { + gsap.to(symbol, { + opacity: 0, + scale: 0, + duration: 0.5, + onComplete: () => symbol.remove(), + }); + } +} diff --git a/frontend/src/app/services/game.service.ts b/frontend/src/app/services/game.service.ts new file mode 100644 index 0000000..4f0509a --- /dev/null +++ b/frontend/src/app/services/game.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +export interface Game { + id: string; + name: string; + description: string; + imageUrl: string; + minBet: number; + maxBet: number; + rtp: number; + lastWin: number; + winChance: number; + lastWinner: string; + trending: boolean; + maxWin: number; + popularity: number; + volatility: 'low' | 'medium' | 'high'; + features: string[]; +} + +@Injectable({ + providedIn: 'root', +}) +export class GameService { + private readonly INITIAL_GAMES: Game[] = [ + { + id: 'mega-fortune', + name: 'Mega Fortune Dreams', + description: '🔥 Progressive Jackpot at €1.2M - Must Drop Today!', + imageUrl: 'assets/games/mega-fortune.jpg', + minBet: 0.2, + maxBet: 100, + rtp: 96.5, + lastWin: 15789, + winChance: 99.9, + lastWinner: 'VIP Player', + trending: true, + maxWin: 1000000, + popularity: 98, + volatility: 'high', + features: ['Progressive Jackpot', 'Free Spins', 'Multipliers'], + }, + { + id: 'lightning-roulette', + name: 'Lightning Roulette', + description: '⚡️ 500x Multipliers Active - Hot Streak!', + imageUrl: 'assets/games/lightning-roulette.jpg', + minBet: 1, + maxBet: 500, + rtp: 97.1, + lastWin: 23456, + winChance: 99.7, + lastWinner: 'New Player', + trending: true, + maxWin: 500000, + popularity: 95, + volatility: 'medium', + features: ['Lightning Multipliers', 'Live Dealer', 'Instant Wins'], + }, + ]; + + private readonly STAT_RANGES = { + WIN: { + MIN: 10000, + MAX: 50000, + }, + WIN_CHANCE: { + MIN: 99, + MAX: 100, + }, + POPULARITY: { + MIN: 80, + MAX: 100, + }, + }; + + private readonly games = new BehaviorSubject(this.INITIAL_GAMES); + readonly games$ = this.games.asObservable(); + + updateGameStats(): void { + const updatedGames = this.games.value.map((game) => ({ + ...game, + ...this.generateNewStats(), + })); + this.games.next(updatedGames); + } + + getGameById(id: string): Game | undefined { + return this.games.value.find((game) => game.id === id); + } + + private generateNewStats(): Partial { + return { + lastWin: this.getRandomInRange(this.STAT_RANGES.WIN), + winChance: this.getRandomInRange(this.STAT_RANGES.WIN_CHANCE), + popularity: this.getRandomInRange(this.STAT_RANGES.POPULARITY), + }; + } + + private getRandomInRange(range: { MIN: number; MAX: number }): number { + return Math.floor(Math.random() * (range.MAX - range.MIN)) + range.MIN; + } +} diff --git a/frontend/src/app/services/jackpot.service.ts b/frontend/src/app/services/jackpot.service.ts new file mode 100644 index 0000000..d13928d --- /dev/null +++ b/frontend/src/app/services/jackpot.service.ts @@ -0,0 +1,130 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class JackpotService { + private readonly INITIAL_JACKPOT = 1234567; + private readonly INITIAL_TIME = '04:59'; + private readonly UPDATE_INTERVAL = 2000; + + private readonly INCREASE_THRESHOLDS = { + MEGA: 0.997, + BONUS: 0.97, + BASE: 0.5, + }; + + private readonly INCREASE_RANGES = { + MEGA: { + MIN: 30000, + MAX: 100000, + }, + BONUS: { + MIN: 2000, + MAX: 15000, + }, + BASE: { + MIN: 100, + MAX: 1000, + }, + }; + + private readonly TIME_LIMITS = { + MINUTES: 4, + SECONDS: 59, + URGENT_THRESHOLD: 30, + }; + + private readonly jackpot = new BehaviorSubject(this.INITIAL_JACKPOT); + private readonly timeLeft = new BehaviorSubject(this.INITIAL_TIME); + private lastUpdateTime = Date.now(); + private minutes = this.TIME_LIMITS.MINUTES; + private seconds = this.TIME_LIMITS.SECONDS; + + readonly currentJackpot$ = this.jackpot.asObservable(); + readonly timeLeft$ = this.timeLeft.asObservable(); + + updateJackpot(): void { + if (!this.shouldUpdate()) return; + + const increase = this.calculateIncrease(); + if (increase > 0) { + this.updateJackpotValue(increase); + this.lastUpdateTime = Date.now(); + } + } + + updateTimeLeft(): void { + this.updateTimers(); + this.updateTimeDisplay(); + } + + isUrgent(): boolean { + return this.minutes === 0 && this.seconds <= this.TIME_LIMITS.URGENT_THRESHOLD; + } + + private shouldUpdate(): boolean { + return Date.now() - this.lastUpdateTime >= this.UPDATE_INTERVAL; + } + + private calculateIncrease(): number { + const random = Math.random(); + + if (random > this.INCREASE_THRESHOLDS.MEGA) { + return this.getRandomIncrease(this.INCREASE_RANGES.MEGA); + } + + if (random > this.INCREASE_THRESHOLDS.BONUS) { + return this.getRandomIncrease(this.INCREASE_RANGES.BONUS); + } + + if (random > this.INCREASE_THRESHOLDS.BASE) { + return this.getRandomIncrease(this.INCREASE_RANGES.BASE); + } + + return 0; + } + + private getRandomIncrease(range: { MIN: number; MAX: number }): number { + return Math.floor(Math.random() * (range.MAX - range.MIN) + range.MIN); + } + + private updateJackpotValue(increase: number): void { + this.jackpot.next(this.jackpot.value + increase); + } + + private updateTimers(): void { + if (this.seconds === 0) { + this.handleMinuteChange(); + } else { + this.seconds--; + } + } + + private handleMinuteChange(): void { + if (this.minutes === 0) { + this.resetTimers(); + } else { + this.minutes--; + this.seconds = this.TIME_LIMITS.SECONDS; + } + } + + private resetTimers(): void { + this.minutes = this.TIME_LIMITS.MINUTES; + this.seconds = this.TIME_LIMITS.SECONDS; + } + + private updateTimeDisplay(): void { + this.timeLeft.next(this.formatTime()); + } + + private formatTime(): string { + return `${this.padNumber(this.minutes)}:${this.padNumber(this.seconds)}`; + } + + private padNumber(num: number): string { + return num.toString().padStart(2, '0'); + } +} diff --git a/frontend/src/app/services/popup.service.ts b/frontend/src/app/services/popup.service.ts new file mode 100644 index 0000000..2e8ec99 --- /dev/null +++ b/frontend/src/app/services/popup.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +export interface Popup { + title: string; + message: string; + type: 'win' | 'offer' | 'urgent' | 'fomo'; + cta: string; + expires?: string; + subMessage?: string; + imageUrl?: string; +} + +@Injectable({ + providedIn: 'root', +}) +export class PopupService { + private readonly POPUP_TEMPLATES: Popup[] = [ + { + title: '🎯 VIP OFFER', + message: 'Enhanced RTP + 500% Bonus on Next 5 Deposits', + subMessage: 'Limited availability - 3 spots remaining', + type: 'urgent', + cta: 'Claim VIP Bonus', + expires: '5:00', + }, + { + title: '🎰 BIG WIN ALERT', + message: 'Recent win: €89,432 on minimum bet!', + subMessage: 'Game is hot - Enhanced win rate active', + type: 'win', + cta: 'Play Now', + }, + ]; + + private readonly DISPLAY_CONFIG = { + MIN_INTERVAL: 30000, + AUTO_CLOSE_DELAY: 8000, + SHOW_CHANCE: 0.7, + }; + + private readonly popupState = new BehaviorSubject(false); + private readonly currentPopup = new BehaviorSubject(null); + private lastPopupTime = 0; + + readonly showPopup$ = this.popupState.asObservable(); + readonly currentPopup$ = this.currentPopup.asObservable(); + + showRandomPopup(): void { + if (!this.shouldShowPopup()) return; + + const popup = this.getRandomPopup(); + this.displayPopup(popup); + this.scheduleAutoClose(); + this.updateLastPopupTime(); + } + + showSpecificPopup(popup: Popup): void { + if (!this.shouldShowPopup()) return; + + this.displayPopup(popup); + this.updateLastPopupTime(); + } + + closePopup(): void { + this.popupState.next(false); + } + + private shouldShowPopup(): boolean { + const now = Date.now(); + const timeSinceLastPopup = now - this.lastPopupTime; + const isMinIntervalPassed = timeSinceLastPopup >= this.DISPLAY_CONFIG.MIN_INTERVAL; + const isCurrentlyHidden = !this.popupState.value; + const isRandomChanceSuccess = Math.random() <= this.DISPLAY_CONFIG.SHOW_CHANCE; + + return isMinIntervalPassed && isCurrentlyHidden && isRandomChanceSuccess; + } + + private getRandomPopup(): Popup { + const randomIndex = Math.floor(Math.random() * this.POPUP_TEMPLATES.length); + return this.POPUP_TEMPLATES[randomIndex]; + } + + private displayPopup(popup: Popup): void { + this.currentPopup.next(popup); + this.popupState.next(true); + } + + private scheduleAutoClose(): void { + setTimeout(() => this.closePopup(), this.DISPLAY_CONFIG.AUTO_CLOSE_DELAY); + } + + private updateLastPopupTime(): void { + this.lastPopupTime = Date.now(); + } +} diff --git a/frontend/src/app/services/winner.service.ts b/frontend/src/app/services/winner.service.ts new file mode 100644 index 0000000..155ba3b --- /dev/null +++ b/frontend/src/app/services/winner.service.ts @@ -0,0 +1,120 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +export interface Winner { + name: string; + amount: number; + game: string; + timestamp: Date; + isVIP: boolean; + betAmount?: number; + multiplier?: number; +} + +@Injectable({ + providedIn: 'root', +}) +export class WinnerService { + private readonly INITIAL_ONLINE_PLAYERS = 2547; + private readonly TOTAL_PLAYERS_TODAY = 15789; + private readonly TOTAL_WINNERS_TODAY = 12453; + private readonly VIP_CHANCE = 0.3; + private readonly MAX_RECENT_WINNERS = 10; + + private readonly PLAYER_NAMES = { + FIRST: ['Alex', 'Maria', 'John', 'Sarah', 'Mike', 'Lisa', 'David', 'Emma'], + LAST: ['K.', 'S.', 'M.', 'L.', 'R.', 'T.', 'B.', 'W.'], + }; + + private readonly ONLINE_PLAYERS_LIMITS = { + MIN: 2000, + MAX: 3500, + CHANGE_RANGE: 15, + }; + + private readonly winners = new BehaviorSubject([]); + private readonly onlinePlayers = new BehaviorSubject(this.INITIAL_ONLINE_PLAYERS); + + readonly recentWinners$ = this.winners.asObservable(); + readonly onlinePlayers$ = this.onlinePlayers.asObservable(); + + constructor() { + this.initializeWinners(); + } + + generateNewWinner(game: string, baseAmount: number): void { + const winner = this.createWinner(game, baseAmount); + this.updateWinnersList(winner); + } + + updateOnlinePlayers(): void { + const currentCount = this.onlinePlayers.value; + const newCount = this.calculateNewPlayerCount(currentCount); + this.onlinePlayers.next(newCount); + } + + getTotalPlayersToday(): number { + return this.TOTAL_PLAYERS_TODAY; + } + + getTotalWinnersToday(): number { + return this.TOTAL_WINNERS_TODAY; + } + + private initializeWinners(): void { + const initialWinners = [ + this.createWinner('Mega Fortune Dreams', 15432), + this.createWinner('Lightning Roulette', 8745), + this.createWinner('Golden Tiger', 12321), + ]; + this.winners.next(initialWinners); + } + + private createWinner(game: string, baseAmount: number): Winner { + const betAmount = this.calculateBetAmount(); + const multiplier = Math.floor(baseAmount / betAmount); + + return { + name: this.generateRandomName(), + amount: baseAmount, + game, + timestamp: new Date(), + isVIP: Math.random() > this.VIP_CHANCE, + betAmount, + multiplier, + }; + } + + private calculateBetAmount(): number { + return Math.floor(Math.random() * 100) + 10; + } + + private updateWinnersList(winner: Winner): void { + const currentWinners = this.winners.value; + const updatedWinners = [winner, ...currentWinners]; + + if (updatedWinners.length > this.MAX_RECENT_WINNERS) { + updatedWinners.pop(); + } + + this.winners.next(updatedWinners); + } + + private generateRandomName(): string { + const firstName = this.getRandomArrayElement(this.PLAYER_NAMES.FIRST); + const lastName = this.getRandomArrayElement(this.PLAYER_NAMES.LAST); + return `${firstName} ${lastName}`; + } + + private calculateNewPlayerCount(currentCount: number): number { + const change = Math.floor(Math.random() * this.ONLINE_PLAYERS_LIMITS.CHANGE_RANGE) - 5; + return Math.max( + this.ONLINE_PLAYERS_LIMITS.MIN, + Math.min(this.ONLINE_PLAYERS_LIMITS.MAX, currentCount + change) + ); + } + + private getRandomArrayElement(array: T[]): T { + return array[Math.floor(Math.random() * array.length)]; + } +} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..843fec3 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,218 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{html,ts}", + ], + theme: { + extend: { + animation: { + 'fadeIn': 'fadeIn 0.3s ease-out', + 'backdropBlur': 'backdropBlur 0.4s ease-out', + 'modalSlideIn': 'modalSlideIn 0.5s cubic-bezier(0.16,1,0.3,1)', + 'slideDown': 'slideDown 0.6s ease-out', + 'slideUp': 'slideUp 0.6s ease-out', + 'scaleIn': 'scaleIn 0.8s cubic-bezier(0.16,1,0.3,1)', + 'spinAndBounce': 'spinAndBounce 3s ease-in-out infinite', + 'glow': 'glow 4s ease-in-out infinite', + 'marquee': 'marquee 30s linear infinite', + 'float': 'float 6s ease-in-out infinite', + 'tiltAndGlow': 'tiltAndGlow 3s ease-in-out infinite', + 'shimmer': 'shimmer 2s linear infinite', + 'morphBackground': 'morphBackground 10s ease-in-out infinite', + 'elasticScale': 'elasticScale 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55)', + 'bounceAndFade': 'bounceAndFade 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55)', + 'rotateAndScale': 'rotateAndScale 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55)', + 'pulseGlow': 'pulseGlow 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', + }, + keyframes: { + fadeIn: { + 'from': { opacity: '0' }, + 'to': { opacity: '1' } + }, + backdropBlur: { + 'from': { + 'backdrop-filter': 'blur(0px)', + 'background-color': 'rgba(0,0,0,0)' + }, + 'to': { + 'backdrop-filter': 'blur(16px)', + 'background-color': 'rgba(0,0,0,0.95)' + } + }, + modalSlideIn: { + 'from': { + opacity: '0', + transform: 'scale(0.95) translateY(10px)' + }, + 'to': { + opacity: '1', + transform: 'scale(1) translateY(0)' + } + }, + slideDown: { + 'from': { + opacity: '0', + transform: 'translateY(-20px)' + }, + 'to': { + opacity: '1', + transform: 'translateY(0)' + } + }, + slideUp: { + 'from': { + opacity: '0', + transform: 'translateY(20px)' + }, + 'to': { + opacity: '1', + transform: 'translateY(0)' + } + }, + scaleIn: { + 'from': { + opacity: '0', + transform: 'scale(0.9)' + }, + 'to': { + opacity: '1', + transform: 'scale(1)' + } + }, + spinAndBounce: { + '0%': { + transform: 'scale(1) rotate(0deg)' + }, + '50%': { + transform: 'scale(1.2) rotate(180deg)' + }, + '100%': { + transform: 'scale(1) rotate(360deg)' + } + }, + glow: { + '0%, 100%': { + opacity: '0.3', + transform: 'scale(1)' + }, + '50%': { + opacity: '0.5', + transform: 'scale(1.05)' + } + }, + marquee: { + '0%': { transform: 'translateX(0)' }, + '100%': { transform: 'translateX(-100%)' } + }, + float: { + '0%, 100%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(-20px)' } + }, + tiltAndGlow: { + '0%, 100%': { + transform: 'perspective(1000px) rotateX(0deg) rotateY(0deg)', + 'box-shadow': '0 0 20px rgba(34,197,94,0.2)' + }, + '50%': { + transform: 'perspective(1000px) rotateX(2deg) rotateY(5deg)', + 'box-shadow': '0 0 40px rgba(34,197,94,0.4)' + } + }, + shimmer: { + '0%': { + 'background-position': '-1000px 0' + }, + '100%': { + 'background-position': '1000px 0' + } + }, + morphBackground: { + '0%, 100%': { + 'border-radius': '60% 40% 30% 70%/60% 30% 70% 40%' + }, + '50%': { + 'border-radius': '30% 60% 70% 40%/50% 60% 30% 60%' + } + }, + elasticScale: { + '0%': { + transform: 'scale(0)' + }, + '60%': { + transform: 'scale(1.1)' + }, + '100%': { + transform: 'scale(1)' + } + }, + bounceAndFade: { + '0%': { + transform: 'scale(0.3)', + opacity: '0' + }, + '50%': { + transform: 'scale(1.05)', + opacity: '0.8' + }, + '100%': { + transform: 'scale(1)', + opacity: '1' + } + }, + rotateAndScale: { + '0%': { + transform: 'rotate(-180deg) scale(0)' + }, + '100%': { + transform: 'rotate(0) scale(1)' + } + }, + pulseGlow: { + '0%, 100%': { + opacity: '1', + transform: 'scale(1)', + filter: 'brightness(1)' + }, + '50%': { + opacity: '0.8', + transform: 'scale(1.05)', + filter: 'brightness(1.2)' + } + } + }, + transitionDelay: { + '100': '100ms', + '200': '200ms', + '300': '300ms', + '400': '400ms', + '500': '500ms', + }, + transitionTimingFunction: { + 'bounce': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', + 'smooth': 'cubic-bezier(0.4, 0, 0.2, 1)', + 'elastic': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', + 'spring': 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', + }, + backdropBlur: { + 'xs': '2px', + '4xl': '72px', + '5xl': '96px', + }, + backgroundImage: { + 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', + 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', + 'shimmer': 'linear-gradient(90deg, transparent, rgba(255,255,255,0.08), transparent)', + } + }, + }, + plugins: [ + require('@tailwindcss/aspect-ratio'), + require('@tailwindcss/forms'), + require('@tailwindcss/typography'), + require('tailwindcss-animated'), + require('tailwindcss-gradients'), + require('tailwindcss-transforms'), + require('tailwindcss-filters'), + require('tailwind-scrollbar-hide'), + ], +} -- 2.47.2 From 5c34014841e0479adaffaae3a4558377fdda9c1b Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 5 Feb 2025 14:40:15 +0100 Subject: [PATCH 02/22] feat: implement animated buttons and winner ticker component --- .../src/app/landing/landing.component.html | 164 ++---------------- frontend/src/app/landing/landing.component.ts | 6 +- .../animated-button.component.ts | 48 +++++ .../game-card/game-card.component.ts | 98 +++++++++++ .../winner-ticker/winner-ticker.component.ts | 32 ++++ 5 files changed, 202 insertions(+), 146 deletions(-) create mode 100644 frontend/src/app/shared/components/animated-button/animated-button.component.ts create mode 100644 frontend/src/app/shared/components/game-card/game-card.component.ts create mode 100644 frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts diff --git a/frontend/src/app/landing/landing.component.html b/frontend/src/app/landing/landing.component.html index 5922ccc..aeaa65c 100644 --- a/frontend/src/app/landing/landing.component.html +++ b/frontend/src/app/landing/landing.component.html @@ -6,20 +6,7 @@ [class.py-1.5]="!isScrolled" >
-
- - 🎰 - - {{ winner.name }} - {{ winner.isVIP ? '(VIP)' : '' }} - won €{{ winner.amount | number }} - ({{ winner.multiplier }}x) - - -
+
@@ -60,15 +47,7 @@ (78.9% Win Rate) - + START PLAYING @@ -157,17 +136,9 @@
- + + CLAIM YOUR €10,000 NOW +
@@ -190,25 +161,7 @@
-
-
- - - 🎰 {{ winner.name }} - {{ - winner.isVIP ? '(VIP)' : '' - }} - turned €{{ winner.betAmount }} into - €{{ winner.amount | number }} - ({{ winner.multiplier }}x) - - -
-
+
@@ -220,79 +173,12 @@
-
-
- -
-
-
-

{{ game.name }}

-
- HOT 🔥 - {{ game.lastWinner }} won €{{ game.lastWin | number }} -
-
-

{{ game.description }}

-
-
- - {{ game.winChance }}% Win Rate - - Max Win: €{{ game.maxWin | number }} -
-
- Min: €{{ game.minBet }} | Max: €{{ game.maxBet }} -
- Popularity: -
-
-
-
-
-
-
- - {{ feature }} - -
- -
-
-
-
-
+
@@ -396,17 +282,9 @@ > Maybe later - + + {{ popup.cta }} +
- + SPIN AGAIN + diff --git a/frontend/src/app/landing/landing.component.ts b/frontend/src/app/landing/landing.component.ts index 4aceb54..1f0ff86 100644 --- a/frontend/src/app/landing/landing.component.ts +++ b/frontend/src/app/landing/landing.component.ts @@ -22,10 +22,14 @@ import { WinnerService } from '../services/winner.service'; import { JackpotService } from '../services/jackpot.service'; import { AnimationService } from '../services/animation.service'; +import { AnimatedButtonComponent } from '../shared/components/animated-button/animated-button.component'; +import { WinnerTickerComponent } from '../shared/components/winner-ticker/winner-ticker.component'; +import { GameCardComponent } from '../shared/components/game-card/game-card.component'; + @Component({ selector: 'app-landing', standalone: true, - imports: [CommonModule], + imports: [CommonModule, AnimatedButtonComponent, WinnerTickerComponent, GameCardComponent], templateUrl: './landing.component.html', changeDetection: ChangeDetectionStrategy.OnPush, animations: [ diff --git a/frontend/src/app/shared/components/animated-button/animated-button.component.ts b/frontend/src/app/shared/components/animated-button/animated-button.component.ts new file mode 100644 index 0000000..959ef17 --- /dev/null +++ b/frontend/src/app/shared/components/animated-button/animated-button.component.ts @@ -0,0 +1,48 @@ +import { Component, Input, Output, EventEmitter, ElementRef } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AnimationService } from '../../../services/animation.service'; + +@Component({ + selector: 'app-animated-button', + standalone: true, + imports: [CommonModule], + template: ` + + `, + styles: [], +}) +export class AnimatedButtonComponent { + @Input() variant: 'primary' | 'secondary' = 'primary'; + @Input() size: 'normal' | 'large' = 'normal'; + @Output() buttonClick = new EventEmitter(); + + constructor(private animationService: AnimationService) {} + + get buttonClass(): string { + const baseClass = + 'relative group font-bold rounded-full transition-all duration-300 ease-out transform-gpu hover:scale-105 will-change-transform'; + const variantClass = + this.variant === 'primary' + ? 'bg-gradient-to-r from-emerald-500 to-emerald-400 text-black hover:shadow-xl hover:shadow-emerald-500/20' + : 'bg-white/10 text-white hover:bg-white/20'; + + return `${baseClass} ${variantClass}`; + } + + handleClick(event: MouseEvent): void { + const elementRef = new ElementRef(event.currentTarget); + this.animationService.animateButtonClick(elementRef); + this.buttonClick.emit(event); + } +} diff --git a/frontend/src/app/shared/components/game-card/game-card.component.ts b/frontend/src/app/shared/components/game-card/game-card.component.ts new file mode 100644 index 0000000..7c8778a --- /dev/null +++ b/frontend/src/app/shared/components/game-card/game-card.component.ts @@ -0,0 +1,98 @@ +import { Component, Input, Output, EventEmitter, ElementRef } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Game } from '../../../services/game.service'; +import { AnimatedButtonComponent } from '../animated-button/animated-button.component'; +import { AnimationService } from '../../../services/animation.service'; + +@Component({ + selector: 'app-game-card', + standalone: true, + imports: [CommonModule, AnimatedButtonComponent], + template: ` +
+
+ +
+
+
+

{{ game.name }}

+
+ + HOT 🔥 + + + {{ game.lastWinner }} won €{{ game.lastWin | number }} + +
+
+

{{ game.description }}

+
+
+ + {{ game.winChance }}% Win Rate + + Max Win: €{{ game.maxWin | number }} +
+
+ + Min: €{{ game.minBet }} | Max: €{{ game.maxBet }} + +
+ Popularity: +
+
+
+
+
+
+
+ + {{ feature }} + +
+ PLAY NOW +
+
+
+
+
+ `, + styles: [], +}) +export class GameCardComponent { + @Input() game!: Game; + @Output() play = new EventEmitter(); + + constructor(private animationService: AnimationService) {} + + onHover(event: MouseEvent): void { + const element = event.currentTarget as HTMLElement; + const elementRef = new ElementRef(element); + this.animationService.animateFloat(elementRef); + } + + onPlay(): void { + this.play.emit(); + } +} diff --git a/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts b/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts new file mode 100644 index 0000000..c22e661 --- /dev/null +++ b/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts @@ -0,0 +1,32 @@ +import { Component, Input, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { default as autoAnimate } from '@formkit/auto-animate'; +import { Winner } from '../../../services/winner.service'; + +@Component({ + selector: 'app-winner-ticker', + standalone: true, + imports: [CommonModule], + template: ` +
+ + 🎰 + + {{ winner.name }} + {{ winner.isVIP ? '(VIP)' : '' }} + won €{{ winner.amount | number }} + ({{ winner.multiplier }}x) + + +
+ `, + styles: [], +}) +export class WinnerTickerComponent implements AfterViewInit { + @Input() winners: Winner[] = []; + @ViewChild('tickerContainer') tickerContainer!: ElementRef; + + ngAfterViewInit(): void { + autoAnimate(this.tickerContainer.nativeElement); + } +} -- 2.47.2 From eede85295e4496f03c3d268eeb30c272415a6a2d Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 5 Feb 2025 14:43:04 +0100 Subject: [PATCH 03/22] refactor: improve code structure and add change detection --- .../src/app/landing/landing.component.html | 4 ++-- frontend/src/app/landing/landing.component.ts | 18 ++++++++++++------ .../animated-button.component.ts | 10 +++++++++- .../game-card/game-card.component.ts | 10 +++++++++- .../winner-ticker/winner-ticker.component.ts | 10 +++++++++- 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/landing/landing.component.html b/frontend/src/app/landing/landing.component.html index aeaa65c..8bb0c62 100644 --- a/frontend/src/app/landing/landing.component.html +++ b/frontend/src/app/landing/landing.component.html @@ -93,9 +93,9 @@
- €{{ currentJackpot$ | async | number }} + €{{ (currentJackpot$ | async) ?? 0 | number }}
diff --git a/frontend/src/app/landing/landing.component.ts b/frontend/src/app/landing/landing.component.ts index 1f0ff86..e2b0afb 100644 --- a/frontend/src/app/landing/landing.component.ts +++ b/frontend/src/app/landing/landing.component.ts @@ -112,14 +112,20 @@ export class LandingComponent implements OnInit, OnDestroy, AfterViewInit { private initializeAnimations(): void { this.animationService.createParticleEffect(this.particleContainer); this.animationService.animateEntrance(this.heroSection); - const gameCards = this.gamesGrid.nativeElement.querySelectorAll('.game-card'); - gameCards.forEach((card: HTMLElement, index: number) => { - this.animationService.animateEntrance(new ElementRef(card), 0.1 * index); - }); - autoAnimate(this.winnersMarquee.nativeElement); + const gameCards = this.gamesGrid?.nativeElement.querySelectorAll('.game-card'); + if (gameCards) { + gameCards.forEach((card: HTMLElement, index: number) => { + this.animationService.animateEntrance(new ElementRef(card), 0.1 * index); + }); + } + this.animationService.animateOnScroll(this.gamesGrid, 'slideUp'); + this.currentJackpot$.pipe(takeUntil(this.destroy$)).subscribe((value) => { - this.animationService.animateJackpotCounter(this.jackpotCounter, value - 1000, value); + if (this.jackpotCounter && value) { + const prevValue = value - Math.floor(Math.random() * 1000 + 500); + this.animationService.animateJackpotCounter(this.jackpotCounter, prevValue, value); + } }); } diff --git a/frontend/src/app/shared/components/animated-button/animated-button.component.ts b/frontend/src/app/shared/components/animated-button/animated-button.component.ts index 959ef17..1ce932d 100644 --- a/frontend/src/app/shared/components/animated-button/animated-button.component.ts +++ b/frontend/src/app/shared/components/animated-button/animated-button.component.ts @@ -1,4 +1,11 @@ -import { Component, Input, Output, EventEmitter, ElementRef } from '@angular/core'; +import { + Component, + Input, + Output, + EventEmitter, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { AnimationService } from '../../../services/animation.service'; @@ -21,6 +28,7 @@ import { AnimationService } from '../../../services/animation.service'; `, styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class AnimatedButtonComponent { @Input() variant: 'primary' | 'secondary' = 'primary'; diff --git a/frontend/src/app/shared/components/game-card/game-card.component.ts b/frontend/src/app/shared/components/game-card/game-card.component.ts index 7c8778a..78dacac 100644 --- a/frontend/src/app/shared/components/game-card/game-card.component.ts +++ b/frontend/src/app/shared/components/game-card/game-card.component.ts @@ -1,4 +1,11 @@ -import { Component, Input, Output, EventEmitter, ElementRef } from '@angular/core'; +import { + Component, + Input, + Output, + EventEmitter, + ElementRef, + ChangeDetectionStrategy, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { Game } from '../../../services/game.service'; import { AnimatedButtonComponent } from '../animated-button/animated-button.component'; @@ -79,6 +86,7 @@ import { AnimationService } from '../../../services/animation.service'; `, styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class GameCardComponent { @Input() game!: Game; diff --git a/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts b/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts index c22e661..7e0c733 100644 --- a/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts +++ b/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts @@ -1,4 +1,11 @@ -import { Component, Input, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; +import { + Component, + Input, + ViewChild, + ElementRef, + AfterViewInit, + ChangeDetectionStrategy, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { default as autoAnimate } from '@formkit/auto-animate'; import { Winner } from '../../../services/winner.service'; @@ -21,6 +28,7 @@ import { Winner } from '../../../services/winner.service'; `, styles: [], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class WinnerTickerComponent implements AfterViewInit { @Input() winners: Winner[] = []; -- 2.47.2 From 5e84afba90e7bbdaac56d7ceea9ad0cb71e25126 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 5 Feb 2025 14:45:36 +0100 Subject: [PATCH 04/22] refactor: remove unused imports and clear image URLs --- frontend/src/app/landing/landing.component.ts | 1 - frontend/src/app/services/game.service.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/landing/landing.component.ts b/frontend/src/app/landing/landing.component.ts index e2b0afb..cd5be3a 100644 --- a/frontend/src/app/landing/landing.component.ts +++ b/frontend/src/app/landing/landing.component.ts @@ -14,7 +14,6 @@ import { Router } from '@angular/router'; import { Subject, interval, Observable } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { animate, style, transition, trigger } from '@angular/animations'; -import { default as autoAnimate } from '@formkit/auto-animate'; import { PopupService } from '../services/popup.service'; import { GameService } from '../services/game.service'; diff --git a/frontend/src/app/services/game.service.ts b/frontend/src/app/services/game.service.ts index 4f0509a..ee6ae08 100644 --- a/frontend/src/app/services/game.service.ts +++ b/frontend/src/app/services/game.service.ts @@ -28,7 +28,7 @@ export class GameService { id: 'mega-fortune', name: 'Mega Fortune Dreams', description: '🔥 Progressive Jackpot at €1.2M - Must Drop Today!', - imageUrl: 'assets/games/mega-fortune.jpg', + imageUrl: '', minBet: 0.2, maxBet: 100, rtp: 96.5, @@ -45,7 +45,7 @@ export class GameService { id: 'lightning-roulette', name: 'Lightning Roulette', description: '⚡️ 500x Multipliers Active - Hot Streak!', - imageUrl: 'assets/games/lightning-roulette.jpg', + imageUrl: '', minBet: 1, maxBet: 500, rtp: 97.1, -- 2.47.2 From 7106ebd0efdb1952a7bc354d5a595b4d5c78a413 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 5 Feb 2025 14:46:44 +0100 Subject: [PATCH 05/22] style(animated-button): add cursor-pointer to button class --- .../components/animated-button/animated-button.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/shared/components/animated-button/animated-button.component.ts b/frontend/src/app/shared/components/animated-button/animated-button.component.ts index 1ce932d..85c433e 100644 --- a/frontend/src/app/shared/components/animated-button/animated-button.component.ts +++ b/frontend/src/app/shared/components/animated-button/animated-button.component.ts @@ -39,7 +39,7 @@ export class AnimatedButtonComponent { get buttonClass(): string { const baseClass = - 'relative group font-bold rounded-full transition-all duration-300 ease-out transform-gpu hover:scale-105 will-change-transform'; + 'cursor-pointer relative group font-bold rounded-full transition-all duration-300 ease-out transform-gpu hover:scale-105 will-change-transform'; const variantClass = this.variant === 'primary' ? 'bg-gradient-to-r from-emerald-500 to-emerald-400 text-black hover:shadow-xl hover:shadow-emerald-500/20' -- 2.47.2 From ff0e03003aa50b31eafa3e35a4c407e38ae55e49 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 5 Feb 2025 14:52:52 +0100 Subject: [PATCH 06/22] style(landing): remove instant withdrawals section and update styles --- .../src/app/landing/landing.component.html | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/landing/landing.component.html b/frontend/src/app/landing/landing.component.html index 8bb0c62..fb8924f 100644 --- a/frontend/src/app/landing/landing.component.html +++ b/frontend/src/app/landing/landing.component.html @@ -142,10 +142,6 @@
-
- - Instant Withdrawals -
24/7 VIP Support @@ -159,7 +155,7 @@
@@ -190,13 +186,6 @@

Elite VIP Status

Up to €50,000 monthly rewards

-
-
⚡️
-

Instant Cashouts

-

Get paid in 5 minutes!

-
@@ -211,9 +200,15 @@

99.9% Win Rate*

Highest odds in the industry!

+
+
🚀
+

Exclusive Offers

+

Unlock special perks and rewards!

+
-
*Terms and conditions apply. Guaranteed winnings based on maximum bonus utilization. Win rate calculated on minimum bets. Withdrawal restrictions and wagering requirements apply. -- 2.47.2 From ea26f5dd80d6f8abbab67a9cbae81bfb193667fa Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 5 Feb 2025 14:33:31 +0100 Subject: [PATCH 07/22] feat(landing): add landing component with animations and services --- .../src/app/landing/landing.component.html | 189 +++++++++++++++--- frontend/src/app/services/game.service.ts | 4 +- 2 files changed, 162 insertions(+), 31 deletions(-) diff --git a/frontend/src/app/landing/landing.component.html b/frontend/src/app/landing/landing.component.html index fb8924f..5922ccc 100644 --- a/frontend/src/app/landing/landing.component.html +++ b/frontend/src/app/landing/landing.component.html @@ -6,7 +6,20 @@ [class.py-1.5]="!isScrolled" >
- +
+ + 🎰 + + {{ winner.name }} + {{ winner.isVIP ? '(VIP)' : '' }} + won €{{ winner.amount | number }} + ({{ winner.multiplier }}x) + + +
@@ -47,7 +60,15 @@ (78.9% Win Rate) - START PLAYING + @@ -93,9 +114,9 @@
- €{{ (currentJackpot$ | async) ?? 0 | number }} + €{{ currentJackpot$ | async | number }}
@@ -136,12 +157,24 @@
- - CLAIM YOUR €10,000 NOW - +
+
+ + Instant Withdrawals +
24/7 VIP Support @@ -155,9 +188,27 @@
- +
+
+ + + 🎰 {{ winner.name }} + {{ + winner.isVIP ? '(VIP)' : '' + }} + turned €{{ winner.betAmount }} into + €{{ winner.amount | number }} + ({{ winner.multiplier }}x) + + +
+
@@ -169,12 +220,79 @@
- - +
+ +
+
+
+

{{ game.name }}

+
+ HOT 🔥 + {{ game.lastWinner }} won €{{ game.lastWin | number }} +
+
+

{{ game.description }}

+
+
+ + {{ game.winChance }}% Win Rate + + Max Win: €{{ game.maxWin | number }} +
+
+ Min: €{{ game.minBet }} | Max: €{{ game.maxBet }} +
+ Popularity: +
+
+
+
+
+
+
+ + {{ feature }} + +
+ +
+
+
+
+
@@ -186,6 +304,13 @@

Elite VIP Status

Up to €50,000 monthly rewards

+
+
⚡️
+

Instant Cashouts

+

Get paid in 5 minutes!

+
@@ -200,15 +325,9 @@

99.9% Win Rate*

Highest odds in the industry!

-
-
🚀
-

Exclusive Offers

-

Unlock special perks and rewards!

-
+
*Terms and conditions apply. Guaranteed winnings based on maximum bonus utilization. Win rate calculated on minimum bets. Withdrawal restrictions and wagering requirements apply. @@ -277,9 +396,17 @@ > Maybe later - - {{ popup.cta }} - +
- - SPIN AGAIN - + SPIN AGAIN +
+ diff --git a/frontend/src/app/services/game.service.ts b/frontend/src/app/services/game.service.ts index ee6ae08..4f0509a 100644 --- a/frontend/src/app/services/game.service.ts +++ b/frontend/src/app/services/game.service.ts @@ -28,7 +28,7 @@ export class GameService { id: 'mega-fortune', name: 'Mega Fortune Dreams', description: '🔥 Progressive Jackpot at €1.2M - Must Drop Today!', - imageUrl: '', + imageUrl: 'assets/games/mega-fortune.jpg', minBet: 0.2, maxBet: 100, rtp: 96.5, @@ -45,7 +45,7 @@ export class GameService { id: 'lightning-roulette', name: 'Lightning Roulette', description: '⚡️ 500x Multipliers Active - Hot Streak!', - imageUrl: '', + imageUrl: 'assets/games/lightning-roulette.jpg', minBet: 1, maxBet: 500, rtp: 97.1, -- 2.47.2 From e6f054f10eb2328c3c111d14ddd6aa68783c34de Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 12 Feb 2025 10:39:49 +0100 Subject: [PATCH 08/22] refactor: rename landing component and remove unused files --- .../src/app/landing/landing.component.html | 481 ------------------ frontend/src/app/landing/landing.component.ts | 262 ---------- .../src/app/services/animation.service.ts | 380 -------------- frontend/src/app/services/game.service.ts | 104 ---- frontend/src/app/services/jackpot.service.ts | 130 ----- frontend/src/app/services/popup.service.ts | 96 ---- frontend/src/app/services/winner.service.ts | 120 ----- .../animated-button.component.ts | 56 -- .../game-card/game-card.component.ts | 106 ---- .../winner-ticker/winner-ticker.component.ts | 40 -- 10 files changed, 1775 deletions(-) delete mode 100644 frontend/src/app/landing/landing.component.html delete mode 100644 frontend/src/app/landing/landing.component.ts delete mode 100644 frontend/src/app/services/animation.service.ts delete mode 100644 frontend/src/app/services/game.service.ts delete mode 100644 frontend/src/app/services/jackpot.service.ts delete mode 100644 frontend/src/app/services/popup.service.ts delete mode 100644 frontend/src/app/services/winner.service.ts delete mode 100644 frontend/src/app/shared/components/animated-button/animated-button.component.ts delete mode 100644 frontend/src/app/shared/components/game-card/game-card.component.ts delete mode 100644 frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts diff --git a/frontend/src/app/landing/landing.component.html b/frontend/src/app/landing/landing.component.html deleted file mode 100644 index 5922ccc..0000000 --- a/frontend/src/app/landing/landing.component.html +++ /dev/null @@ -1,481 +0,0 @@ -
-
-
-
-
- - 🎰 - - {{ winner.name }} - {{ winner.isVIP ? '(VIP)' : '' }} - won €{{ winner.amount | number }} - ({{ winner.multiplier }}x) - - -
-
-
- - -
- -
- -
-
- -
-
-
🎰
-
- 💎 -
-
- 7️⃣ -
-
- 🃏 -
-
- 💰 -
-
- 🎲 -
-
👑
-
-
- -
-
-
-
-
🏆 MEGA JACKPOT GROWING
-
- - €{{ currentJackpot$ | async | number }} - - -
-
Must drop before €2,000,000
-
-
- -
-
-
-
- EXCLUSIVE VIP OFFER -
-

-
START WITH
-
- €10,000 -
-
GUARANTEED WINNINGS*
-

- -
-
- 1000% FIRST DEPOSIT MATCH -
+ 1000 FREE SPINS
-
-
- ⚠️ Offer expires in: {{ timeLeft$ | async }} -
-
- -
- -
- -
-
- - Instant Withdrawals -
-
- - 24/7 VIP Support -
-
- - 100% Win Guarantee* -
-
-
-
- -
-
-
- - - 🎰 {{ winner.name }} - {{ - winner.isVIP ? '(VIP)' : '' - }} - turned €{{ winner.betAmount }} into - €{{ winner.amount | number }} - ({{ winner.multiplier }}x) - - -
-
-
- -
-

- - TOP WINNING GAMES - -

-
-
-
- -
-
-
-

{{ game.name }}

-
- HOT 🔥 - {{ game.lastWinner }} won €{{ game.lastWin | number }} -
-
-

{{ game.description }}

-
-
- - {{ game.winChance }}% Win Rate - - Max Win: €{{ game.maxWin | number }} -
-
- Min: €{{ game.minBet }} | Max: €{{ game.maxBet }} -
- Popularity: -
-
-
-
-
-
-
- - {{ feature }} - -
- -
-
-
-
-
-
-
- -
-
-
💎
-

Elite VIP Status

-

Up to €50,000 monthly rewards

-
-
-
⚡️
-

Instant Cashouts

-

Get paid in 5 minutes!

-
-
-
🎁
-

Daily Rewards

-

Win up to €5,000 daily!

-
-
-
🏆
-

99.9% Win Rate*

-

Highest odds in the industry!

-
-
- - -
- *Terms and conditions apply. Guaranteed winnings based on maximum bonus utilization. Win - rate calculated on minimum bets. Withdrawal restrictions and wagering requirements apply. - Please gamble responsibly. -
-
-
-
-
-
-
-
- -

- {{ popup.title }} -

-

- {{ popup.message }} -

-

- {{ popup.subMessage }} -

- -
- - -
-
- ⏰ Expires in: {{ popup.expires }} -
-
-
-
- -
-
-
- -
-
- 🎰 -
-

- SO CLOSE! -

-

- Just one more spin to win the MEGA JACKPOT! -

-
- - Hot streak detected - Increased win probability activated! - -
- -
-
-
-
-
diff --git a/frontend/src/app/landing/landing.component.ts b/frontend/src/app/landing/landing.component.ts deleted file mode 100644 index cd5be3a..0000000 --- a/frontend/src/app/landing/landing.component.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { - Component, - OnInit, - OnDestroy, - ChangeDetectionStrategy, - ChangeDetectorRef, - NgZone, - ElementRef, - ViewChild, - AfterViewInit, -} from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { Router } from '@angular/router'; -import { Subject, interval, Observable } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { animate, style, transition, trigger } from '@angular/animations'; - -import { PopupService } from '../services/popup.service'; -import { GameService } from '../services/game.service'; -import { WinnerService } from '../services/winner.service'; -import { JackpotService } from '../services/jackpot.service'; -import { AnimationService } from '../services/animation.service'; - -import { AnimatedButtonComponent } from '../shared/components/animated-button/animated-button.component'; -import { WinnerTickerComponent } from '../shared/components/winner-ticker/winner-ticker.component'; -import { GameCardComponent } from '../shared/components/game-card/game-card.component'; - -@Component({ - selector: 'app-landing', - standalone: true, - imports: [CommonModule, AnimatedButtonComponent, WinnerTickerComponent, GameCardComponent], - templateUrl: './landing.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - animations: [ - trigger('fadeSlide', [ - transition(':enter', [ - style({ opacity: 0, transform: 'translateY(20px)' }), - animate( - '0.5s cubic-bezier(0.4, 0, 0.2, 1)', - style({ opacity: 1, transform: 'translateY(0)' }) - ), - ]), - transition(':leave', [ - animate( - '0.5s cubic-bezier(0.4, 0, 0.2, 1)', - style({ opacity: 0, transform: 'translateY(-20px)' }) - ), - ]), - ]), - ], -}) -export class LandingComponent implements OnInit, OnDestroy, AfterViewInit { - private destroy$ = new Subject(); - nearMiss = false; - isScrolled = false; - - @ViewChild('jackpotCounter') jackpotCounter!: ElementRef; - @ViewChild('heroSection') heroSection!: ElementRef; - @ViewChild('gamesGrid') gamesGrid!: ElementRef; - @ViewChild('winnersMarquee') winnersMarquee!: ElementRef; - @ViewChild('particleContainer') particleContainer!: ElementRef; - - readonly showPopup$: Observable; - readonly currentPopup$: Observable; - readonly games$: Observable; - readonly recentWinners$: Observable; - readonly onlinePlayers$: Observable; - readonly currentJackpot$: Observable; - readonly timeLeft$: Observable; - readonly totalPlayersToday: number; - readonly totalWinnersToday: number; - - constructor( - private router: Router, - private cdr: ChangeDetectorRef, - private ngZone: NgZone, - private popupService: PopupService, - private gameService: GameService, - private winnerService: WinnerService, - private jackpotService: JackpotService, - private animationService: AnimationService - ) { - this.showPopup$ = this.popupService.showPopup$; - this.currentPopup$ = this.popupService.currentPopup$; - this.games$ = this.gameService.games$; - this.recentWinners$ = this.winnerService.recentWinners$; - this.onlinePlayers$ = this.winnerService.onlinePlayers$; - this.currentJackpot$ = this.jackpotService.currentJackpot$; - this.timeLeft$ = this.jackpotService.timeLeft$; - this.totalPlayersToday = this.winnerService.getTotalPlayersToday(); - this.totalWinnersToday = this.winnerService.getTotalWinnersToday(); - } - - ngOnInit(): void { - this.initializeTimers(); - this.initializeScrollListener(); - } - - ngAfterViewInit(): void { - this.initializeAnimations(); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - window.removeEventListener('scroll', () => { - this.isScrolled = window.scrollY > 0; - }); - } - - private initializeAnimations(): void { - this.animationService.createParticleEffect(this.particleContainer); - this.animationService.animateEntrance(this.heroSection); - const gameCards = this.gamesGrid?.nativeElement.querySelectorAll('.game-card'); - if (gameCards) { - gameCards.forEach((card: HTMLElement, index: number) => { - this.animationService.animateEntrance(new ElementRef(card), 0.1 * index); - }); - } - - this.animationService.animateOnScroll(this.gamesGrid, 'slideUp'); - - this.currentJackpot$.pipe(takeUntil(this.destroy$)).subscribe((value) => { - if (this.jackpotCounter && value) { - const prevValue = value - Math.floor(Math.random() * 1000 + 500); - this.animationService.animateJackpotCounter(this.jackpotCounter, prevValue, value); - } - }); - } - - private initializeTimers(): void { - this.ngZone.runOutsideAngular(() => { - interval(1500) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.jackpotService.updateJackpot(); - this.cdr.markForCheck(); - }); - interval(3000) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.winnerService.updateOnlinePlayers(); - this.cdr.markForCheck(); - }); - interval(1000) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.jackpotService.updateTimeLeft(); - if (this.jackpotService.isUrgent()) { - this.showUrgentOffer(); - } - this.cdr.markForCheck(); - }); - interval(7000) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - const randomGame = this.gameService.getGameById('mega-fortune'); - if (randomGame) { - const winAmount = Math.floor(Math.random() * 50000) + 10000; - this.winnerService.generateNewWinner(randomGame.name, winAmount); - if (winAmount > 10000) { - this.showBigWinPopup(winAmount); - } - } - this.cdr.markForCheck(); - }); - interval(15000) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.gameService.updateGameStats(); - this.cdr.markForCheck(); - }); - interval(30000) - .pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.popupService.showRandomPopup(); - this.cdr.markForCheck(); - }); - }); - } - - private initializeScrollListener(): void { - window.addEventListener('scroll', () => { - this.isScrolled = window.scrollY > 0; - this.cdr.detectChanges(); - }); - } - - private showUrgentOffer(): void { - this.popupService.showSpecificPopup({ - title: '⚠️ LAST CHANCE!', - message: 'Bonus offer expiring - Lock in 500% now!', - type: 'urgent', - cta: 'Claim Before Timer Ends', - expires: '00:30', - }); - } - - private showBigWinPopup(amount: number): void { - this.popupService.showSpecificPopup({ - title: '🎰 MASSIVE WIN ALERT!', - message: `Player just won €${amount.toLocaleString()} on minimum bet!`, - subMessage: 'Same game still hot - Win rate increased to 99.9%!', - type: 'win', - cta: 'Play Same Game', - }); - } - - closePopup(): void { - this.popupService.closePopup(); - } - - claimBonus(): void { - this.nearMiss = true; - this.cdr.markForCheck(); - - setTimeout(() => { - this.router.navigate(['/register'], { - queryParams: { - bonus: 'welcome1000', - ref: 'landing_hero', - special: 'true', - vip: 'fast-track', - }, - }); - }, 1500); - } - - playNow(gameId: string): void { - const game = this.gameService.getGameById(gameId); - if (!game) return; - - this.popupService.showSpecificPopup({ - title: '🎰 PERFECT TIMING!', - message: `${game.name} is currently at ${game.winChance}% win rate!`, - subMessage: `Last player won €${game.lastWin.toLocaleString()} - Hot streak active!`, - type: 'fomo', - cta: 'Play Now', - }); - - setTimeout(() => { - this.router.navigate(['/game', gameId], { - queryParams: { - ref: 'landing_games', - bonus: 'true', - rtp: 'enhanced', - multiplier: 'active', - }, - }); - }, 2000); - } - - onButtonClick(event: MouseEvent): void { - const button = event.currentTarget as HTMLElement; - this.animationService.animateButtonClick(new ElementRef(button)); - } - - onGameCardHover(event: MouseEvent): void { - const card = event.currentTarget as HTMLElement; - this.animationService.animateFloat(new ElementRef(card)); - } -} diff --git a/frontend/src/app/services/animation.service.ts b/frontend/src/app/services/animation.service.ts deleted file mode 100644 index 34fe89a..0000000 --- a/frontend/src/app/services/animation.service.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { Injectable, ElementRef } from '@angular/core'; -import { gsap } from 'gsap'; -import { ScrollTrigger } from 'gsap/ScrollTrigger'; -import { MotionPathPlugin } from 'gsap/MotionPathPlugin'; - -gsap.registerPlugin(ScrollTrigger, MotionPathPlugin); - -@Injectable({ - providedIn: 'root', -}) -export class AnimationService { - private readonly MEGA_BONUS_THRESHOLD = 25000; - private readonly BONUS_THRESHOLD = 1000; - private readonly FLASH_THRESHOLD = 10000; - - private readonly ANIMATION_DURATIONS = { - MEGA: 3, - BONUS: 2, - BASE: 1, - }; - - private readonly SYMBOLS = { - MONEY: '💰', - SPARKLE: '✨', - GEM: '💎', - STAR: '🌟', - }; - - constructor() { - // Configure GSAP defaults - gsap.config({ - autoSleep: 60, - force3D: true, - nullTargetWarn: false, - }); - } - - animateEntrance(element: ElementRef, delay: number = 0) { - return gsap.from(element.nativeElement, { - duration: 0.6, - opacity: 0, - y: 30, - ease: 'power3.out', - delay, - clearProps: 'all', - }); - } - - animateFloat(element: ElementRef) { - return gsap.to(element.nativeElement, { - duration: 2, - y: '-=20', - ease: 'power1.inOut', - yoyo: true, - repeat: -1, - }); - } - - animateShine(element: ElementRef) { - const shine = gsap.to(element.nativeElement, { - duration: 1.5, - backgroundPosition: '200%', - ease: 'linear', - repeat: -1, - }); - return shine; - } - - animateMorphingBackground(element: ElementRef) { - return gsap.to(element.nativeElement, { - duration: 8, - borderRadius: '60% 40% 30% 70% / 60% 30% 70% 40%', - ease: 'sine.inOut', - repeat: -1, - yoyo: true, - }); - } - - animateOnScroll(element: ElementRef, animation: 'fadeIn' | 'slideUp' | 'scaleIn' = 'fadeIn') { - const animations = { - fadeIn: { - opacity: 0, - y: 0, - duration: 0.6, - }, - slideUp: { - opacity: 0, - y: 50, - duration: 0.8, - }, - scaleIn: { - opacity: 0, - scale: 0.8, - duration: 0.6, - }, - }; - - return gsap.from(element.nativeElement, { - ...animations[animation], - ease: 'power2.out', - scrollTrigger: { - trigger: element.nativeElement, - start: 'top bottom-=100', - toggleActions: 'play none none reverse', - }, - }); - } - - createParticleEffect(container: ElementRef, particleCount: number = 20): void { - const particles = Array.from({ length: particleCount }, () => this.createParticle(container)); - particles.forEach((particle) => this.animateParticle(particle)); - } - - private createParticle(container: ElementRef): HTMLElement { - const particle = document.createElement('div'); - particle.className = 'absolute w-2 h-2 bg-emerald-500/20 rounded-full'; - container.nativeElement.appendChild(particle); - - gsap.set(particle, { - x: gsap.utils.random(0, container.nativeElement.offsetWidth), - y: gsap.utils.random(0, container.nativeElement.offsetHeight), - }); - - return particle; - } - - private animateParticle(particle: HTMLElement): void { - gsap.to(particle, { - duration: gsap.utils.random(2, 4), - x: '+=50', - y: '-=50', - opacity: 0, - scale: 0, - ease: 'none', - repeat: -1, - onRepeat: () => this.resetParticle(particle), - }); - } - - private resetParticle(particle: HTMLElement): void { - gsap.set(particle, { - x: gsap.utils.random(0, particle.parentElement!.offsetWidth), - y: gsap.utils.random(0, particle.parentElement!.offsetHeight), - opacity: 1, - scale: 1, - }); - } - - animateButtonClick(element: ElementRef): gsap.core.Timeline { - return gsap - .timeline() - .to(element.nativeElement, { scale: 0.95, duration: 0.1 }) - .to(element.nativeElement, { scale: 1, duration: 0.2, ease: 'elastic.out(1, 0.3)' }); - } - - animateSuccess(element: ElementRef): gsap.core.Timeline { - return gsap - .timeline() - .to(element.nativeElement, { scale: 1.2, duration: 0.2, ease: 'power2.out' }) - .to(element.nativeElement, { scale: 1, duration: 0.5, ease: 'elastic.out(1, 0.3)' }); - } - - animateJackpotCounter( - element: ElementRef, - startValue: number, - endValue: number - ): gsap.core.Timeline { - const container = this.prepareContainer(element); - const increase = endValue - startValue; - const timeline = gsap.timeline(); - - if (increase > this.FLASH_THRESHOLD) { - this.addFlashEffect(timeline, container, element); - } - - this.addCounterAnimation(timeline, element, startValue, endValue); - - if (increase > this.MEGA_BONUS_THRESHOLD) { - this.addMegaBonusEffect(element); - } else if (increase > this.BONUS_THRESHOLD) { - this.addBonusEffect(element); - } - - return timeline; - } - - private prepareContainer(element: ElementRef): HTMLElement { - const container = element.nativeElement.parentElement; - container.style.position = 'relative'; - this.cleanupExistingEffects(container); - return container; - } - - private cleanupExistingEffects(container: HTMLElement): void { - const existingEffects = container.querySelectorAll('.jackpot-effect'); - existingEffects.forEach((effect: Element) => effect.remove()); - } - - private addFlashEffect( - timeline: gsap.core.Timeline, - container: HTMLElement, - element: ElementRef - ): void { - const flash = this.createFlashElement(); - container.appendChild(flash); - - timeline - .to(flash, { - opacity: 1, - duration: 0.3, - yoyo: true, - repeat: 2, - onComplete: () => flash.remove(), - }) - .to( - element.nativeElement, - { - color: '#FFD700', - textShadow: '0 0 20px rgba(255,215,0,0.8)', - scale: 1.1, - duration: 0.6, - yoyo: true, - repeat: 1, - }, - '<' - ); - } - - private createFlashElement(): HTMLElement { - const flash = document.createElement('div'); - flash.className = - 'jackpot-effect absolute inset-0 bg-yellow-400/20 rounded-xl backdrop-blur-sm z-10'; - return flash; - } - - private addCounterAnimation( - timeline: gsap.core.Timeline, - element: ElementRef, - startValue: number, - endValue: number - ): void { - const obj = { value: startValue }; - timeline.to(obj, { - duration: this.calculateDuration(endValue - startValue), - value: endValue, - ease: 'power1.inOut', - onUpdate: () => this.updateCounter(obj.value, endValue, element), - onComplete: () => this.resetElementStyles(element, endValue), - }); - } - - private updateCounter(currentValue: number, endValue: number, element: ElementRef): void { - const progress = currentValue / endValue; - const fluctuation = Math.random() * (100 * (1 - progress)) - 50 * (1 - progress); - const displayValue = Math.floor(currentValue + fluctuation); - element.nativeElement.textContent = '€' + displayValue.toLocaleString(); - } - - private resetElementStyles(element: ElementRef, finalValue: number): void { - element.nativeElement.textContent = '€' + finalValue.toLocaleString(); - element.nativeElement.style.color = ''; - element.nativeElement.style.textShadow = ''; - element.nativeElement.style.transform = ''; - } - - private calculateDuration(increase: number): number { - if (increase > this.MEGA_BONUS_THRESHOLD) return this.ANIMATION_DURATIONS.MEGA; - if (increase > this.BONUS_THRESHOLD) return this.ANIMATION_DURATIONS.BONUS; - return this.ANIMATION_DURATIONS.BASE; - } - - private addMegaBonusEffect(element: ElementRef): void { - const effectContainer = this.createEffectContainer(element); - this.addGlowEffect(effectContainer, true); - this.addFloatingSymbols(element, effectContainer, 10, 50); - } - - private addBonusEffect(element: ElementRef): void { - const effectContainer = this.createEffectContainer(element); - this.addGlowEffect(effectContainer, false); - this.addFloatingSymbols(element, effectContainer, 5, 30); - } - - private createEffectContainer(element: ElementRef): HTMLElement { - const container = element.nativeElement.parentElement; - const effectContainer = document.createElement('div'); - effectContainer.className = - 'jackpot-effect absolute inset-0 pointer-events-none overflow-hidden z-20'; - container.appendChild(effectContainer); - return effectContainer; - } - - private addGlowEffect(container: HTMLElement, isMega: boolean): void { - const config = isMega - ? { - shadow: '0 0 30px rgba(255,215,0,0.8), 0 0 60px rgba(255,165,0,0.6)', - scale: 1.1, - duration: 1.5, - repeat: 2, - } - : { - shadow: '0 0 20px rgba(255,215,0,0.4)', - scale: 1.05, - duration: 0.8, - repeat: 1, - }; - - gsap.to(container, { - boxShadow: config.shadow, - scale: config.scale, - opacity: 0, - duration: config.duration, - repeat: config.repeat, - ease: 'power2.inOut', - onComplete: () => container.remove(), - }); - } - - private addFloatingSymbols( - element: ElementRef, - container: HTMLElement, - count: number, - radius: number - ): void { - const rect = element.nativeElement.getBoundingClientRect(); - const centerX = rect.width / 2; - const centerY = rect.height / 2; - const symbols = Object.values(this.SYMBOLS); - - Array.from({ length: count }).forEach((_, i) => { - const symbol = this.createSymbol(symbols, container); - const angle = (i / count) * Math.PI * 2; - this.animateSymbol(symbol, centerX, centerY, angle, radius); - }); - } - - private createSymbol(symbols: string[], container: HTMLElement): HTMLElement { - const symbol = document.createElement('div'); - symbol.textContent = symbols[Math.floor(Math.random() * symbols.length)]; - symbol.className = 'jackpot-effect absolute text-2xl'; - container.appendChild(symbol); - return symbol; - } - - private animateSymbol( - symbol: HTMLElement, - centerX: number, - centerY: number, - angle: number, - radius: number - ): void { - gsap.fromTo( - symbol, - { - x: centerX, - y: centerY, - opacity: 0, - scale: 0, - }, - { - x: centerX + Math.cos(angle) * radius, - y: centerY + Math.sin(angle) * radius, - opacity: 1, - scale: 1, - duration: 1.5, - ease: 'back.out(1.2)', - onComplete: () => this.fadeOutSymbol(symbol), - } - ); - } - - private fadeOutSymbol(symbol: HTMLElement): void { - gsap.to(symbol, { - opacity: 0, - scale: 0, - duration: 0.5, - onComplete: () => symbol.remove(), - }); - } -} diff --git a/frontend/src/app/services/game.service.ts b/frontend/src/app/services/game.service.ts deleted file mode 100644 index 4f0509a..0000000 --- a/frontend/src/app/services/game.service.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; - -export interface Game { - id: string; - name: string; - description: string; - imageUrl: string; - minBet: number; - maxBet: number; - rtp: number; - lastWin: number; - winChance: number; - lastWinner: string; - trending: boolean; - maxWin: number; - popularity: number; - volatility: 'low' | 'medium' | 'high'; - features: string[]; -} - -@Injectable({ - providedIn: 'root', -}) -export class GameService { - private readonly INITIAL_GAMES: Game[] = [ - { - id: 'mega-fortune', - name: 'Mega Fortune Dreams', - description: '🔥 Progressive Jackpot at €1.2M - Must Drop Today!', - imageUrl: 'assets/games/mega-fortune.jpg', - minBet: 0.2, - maxBet: 100, - rtp: 96.5, - lastWin: 15789, - winChance: 99.9, - lastWinner: 'VIP Player', - trending: true, - maxWin: 1000000, - popularity: 98, - volatility: 'high', - features: ['Progressive Jackpot', 'Free Spins', 'Multipliers'], - }, - { - id: 'lightning-roulette', - name: 'Lightning Roulette', - description: '⚡️ 500x Multipliers Active - Hot Streak!', - imageUrl: 'assets/games/lightning-roulette.jpg', - minBet: 1, - maxBet: 500, - rtp: 97.1, - lastWin: 23456, - winChance: 99.7, - lastWinner: 'New Player', - trending: true, - maxWin: 500000, - popularity: 95, - volatility: 'medium', - features: ['Lightning Multipliers', 'Live Dealer', 'Instant Wins'], - }, - ]; - - private readonly STAT_RANGES = { - WIN: { - MIN: 10000, - MAX: 50000, - }, - WIN_CHANCE: { - MIN: 99, - MAX: 100, - }, - POPULARITY: { - MIN: 80, - MAX: 100, - }, - }; - - private readonly games = new BehaviorSubject(this.INITIAL_GAMES); - readonly games$ = this.games.asObservable(); - - updateGameStats(): void { - const updatedGames = this.games.value.map((game) => ({ - ...game, - ...this.generateNewStats(), - })); - this.games.next(updatedGames); - } - - getGameById(id: string): Game | undefined { - return this.games.value.find((game) => game.id === id); - } - - private generateNewStats(): Partial { - return { - lastWin: this.getRandomInRange(this.STAT_RANGES.WIN), - winChance: this.getRandomInRange(this.STAT_RANGES.WIN_CHANCE), - popularity: this.getRandomInRange(this.STAT_RANGES.POPULARITY), - }; - } - - private getRandomInRange(range: { MIN: number; MAX: number }): number { - return Math.floor(Math.random() * (range.MAX - range.MIN)) + range.MIN; - } -} diff --git a/frontend/src/app/services/jackpot.service.ts b/frontend/src/app/services/jackpot.service.ts deleted file mode 100644 index d13928d..0000000 --- a/frontend/src/app/services/jackpot.service.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; - -@Injectable({ - providedIn: 'root', -}) -export class JackpotService { - private readonly INITIAL_JACKPOT = 1234567; - private readonly INITIAL_TIME = '04:59'; - private readonly UPDATE_INTERVAL = 2000; - - private readonly INCREASE_THRESHOLDS = { - MEGA: 0.997, - BONUS: 0.97, - BASE: 0.5, - }; - - private readonly INCREASE_RANGES = { - MEGA: { - MIN: 30000, - MAX: 100000, - }, - BONUS: { - MIN: 2000, - MAX: 15000, - }, - BASE: { - MIN: 100, - MAX: 1000, - }, - }; - - private readonly TIME_LIMITS = { - MINUTES: 4, - SECONDS: 59, - URGENT_THRESHOLD: 30, - }; - - private readonly jackpot = new BehaviorSubject(this.INITIAL_JACKPOT); - private readonly timeLeft = new BehaviorSubject(this.INITIAL_TIME); - private lastUpdateTime = Date.now(); - private minutes = this.TIME_LIMITS.MINUTES; - private seconds = this.TIME_LIMITS.SECONDS; - - readonly currentJackpot$ = this.jackpot.asObservable(); - readonly timeLeft$ = this.timeLeft.asObservable(); - - updateJackpot(): void { - if (!this.shouldUpdate()) return; - - const increase = this.calculateIncrease(); - if (increase > 0) { - this.updateJackpotValue(increase); - this.lastUpdateTime = Date.now(); - } - } - - updateTimeLeft(): void { - this.updateTimers(); - this.updateTimeDisplay(); - } - - isUrgent(): boolean { - return this.minutes === 0 && this.seconds <= this.TIME_LIMITS.URGENT_THRESHOLD; - } - - private shouldUpdate(): boolean { - return Date.now() - this.lastUpdateTime >= this.UPDATE_INTERVAL; - } - - private calculateIncrease(): number { - const random = Math.random(); - - if (random > this.INCREASE_THRESHOLDS.MEGA) { - return this.getRandomIncrease(this.INCREASE_RANGES.MEGA); - } - - if (random > this.INCREASE_THRESHOLDS.BONUS) { - return this.getRandomIncrease(this.INCREASE_RANGES.BONUS); - } - - if (random > this.INCREASE_THRESHOLDS.BASE) { - return this.getRandomIncrease(this.INCREASE_RANGES.BASE); - } - - return 0; - } - - private getRandomIncrease(range: { MIN: number; MAX: number }): number { - return Math.floor(Math.random() * (range.MAX - range.MIN) + range.MIN); - } - - private updateJackpotValue(increase: number): void { - this.jackpot.next(this.jackpot.value + increase); - } - - private updateTimers(): void { - if (this.seconds === 0) { - this.handleMinuteChange(); - } else { - this.seconds--; - } - } - - private handleMinuteChange(): void { - if (this.minutes === 0) { - this.resetTimers(); - } else { - this.minutes--; - this.seconds = this.TIME_LIMITS.SECONDS; - } - } - - private resetTimers(): void { - this.minutes = this.TIME_LIMITS.MINUTES; - this.seconds = this.TIME_LIMITS.SECONDS; - } - - private updateTimeDisplay(): void { - this.timeLeft.next(this.formatTime()); - } - - private formatTime(): string { - return `${this.padNumber(this.minutes)}:${this.padNumber(this.seconds)}`; - } - - private padNumber(num: number): string { - return num.toString().padStart(2, '0'); - } -} diff --git a/frontend/src/app/services/popup.service.ts b/frontend/src/app/services/popup.service.ts deleted file mode 100644 index 2e8ec99..0000000 --- a/frontend/src/app/services/popup.service.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; - -export interface Popup { - title: string; - message: string; - type: 'win' | 'offer' | 'urgent' | 'fomo'; - cta: string; - expires?: string; - subMessage?: string; - imageUrl?: string; -} - -@Injectable({ - providedIn: 'root', -}) -export class PopupService { - private readonly POPUP_TEMPLATES: Popup[] = [ - { - title: '🎯 VIP OFFER', - message: 'Enhanced RTP + 500% Bonus on Next 5 Deposits', - subMessage: 'Limited availability - 3 spots remaining', - type: 'urgent', - cta: 'Claim VIP Bonus', - expires: '5:00', - }, - { - title: '🎰 BIG WIN ALERT', - message: 'Recent win: €89,432 on minimum bet!', - subMessage: 'Game is hot - Enhanced win rate active', - type: 'win', - cta: 'Play Now', - }, - ]; - - private readonly DISPLAY_CONFIG = { - MIN_INTERVAL: 30000, - AUTO_CLOSE_DELAY: 8000, - SHOW_CHANCE: 0.7, - }; - - private readonly popupState = new BehaviorSubject(false); - private readonly currentPopup = new BehaviorSubject(null); - private lastPopupTime = 0; - - readonly showPopup$ = this.popupState.asObservable(); - readonly currentPopup$ = this.currentPopup.asObservable(); - - showRandomPopup(): void { - if (!this.shouldShowPopup()) return; - - const popup = this.getRandomPopup(); - this.displayPopup(popup); - this.scheduleAutoClose(); - this.updateLastPopupTime(); - } - - showSpecificPopup(popup: Popup): void { - if (!this.shouldShowPopup()) return; - - this.displayPopup(popup); - this.updateLastPopupTime(); - } - - closePopup(): void { - this.popupState.next(false); - } - - private shouldShowPopup(): boolean { - const now = Date.now(); - const timeSinceLastPopup = now - this.lastPopupTime; - const isMinIntervalPassed = timeSinceLastPopup >= this.DISPLAY_CONFIG.MIN_INTERVAL; - const isCurrentlyHidden = !this.popupState.value; - const isRandomChanceSuccess = Math.random() <= this.DISPLAY_CONFIG.SHOW_CHANCE; - - return isMinIntervalPassed && isCurrentlyHidden && isRandomChanceSuccess; - } - - private getRandomPopup(): Popup { - const randomIndex = Math.floor(Math.random() * this.POPUP_TEMPLATES.length); - return this.POPUP_TEMPLATES[randomIndex]; - } - - private displayPopup(popup: Popup): void { - this.currentPopup.next(popup); - this.popupState.next(true); - } - - private scheduleAutoClose(): void { - setTimeout(() => this.closePopup(), this.DISPLAY_CONFIG.AUTO_CLOSE_DELAY); - } - - private updateLastPopupTime(): void { - this.lastPopupTime = Date.now(); - } -} diff --git a/frontend/src/app/services/winner.service.ts b/frontend/src/app/services/winner.service.ts deleted file mode 100644 index 155ba3b..0000000 --- a/frontend/src/app/services/winner.service.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; - -export interface Winner { - name: string; - amount: number; - game: string; - timestamp: Date; - isVIP: boolean; - betAmount?: number; - multiplier?: number; -} - -@Injectable({ - providedIn: 'root', -}) -export class WinnerService { - private readonly INITIAL_ONLINE_PLAYERS = 2547; - private readonly TOTAL_PLAYERS_TODAY = 15789; - private readonly TOTAL_WINNERS_TODAY = 12453; - private readonly VIP_CHANCE = 0.3; - private readonly MAX_RECENT_WINNERS = 10; - - private readonly PLAYER_NAMES = { - FIRST: ['Alex', 'Maria', 'John', 'Sarah', 'Mike', 'Lisa', 'David', 'Emma'], - LAST: ['K.', 'S.', 'M.', 'L.', 'R.', 'T.', 'B.', 'W.'], - }; - - private readonly ONLINE_PLAYERS_LIMITS = { - MIN: 2000, - MAX: 3500, - CHANGE_RANGE: 15, - }; - - private readonly winners = new BehaviorSubject([]); - private readonly onlinePlayers = new BehaviorSubject(this.INITIAL_ONLINE_PLAYERS); - - readonly recentWinners$ = this.winners.asObservable(); - readonly onlinePlayers$ = this.onlinePlayers.asObservable(); - - constructor() { - this.initializeWinners(); - } - - generateNewWinner(game: string, baseAmount: number): void { - const winner = this.createWinner(game, baseAmount); - this.updateWinnersList(winner); - } - - updateOnlinePlayers(): void { - const currentCount = this.onlinePlayers.value; - const newCount = this.calculateNewPlayerCount(currentCount); - this.onlinePlayers.next(newCount); - } - - getTotalPlayersToday(): number { - return this.TOTAL_PLAYERS_TODAY; - } - - getTotalWinnersToday(): number { - return this.TOTAL_WINNERS_TODAY; - } - - private initializeWinners(): void { - const initialWinners = [ - this.createWinner('Mega Fortune Dreams', 15432), - this.createWinner('Lightning Roulette', 8745), - this.createWinner('Golden Tiger', 12321), - ]; - this.winners.next(initialWinners); - } - - private createWinner(game: string, baseAmount: number): Winner { - const betAmount = this.calculateBetAmount(); - const multiplier = Math.floor(baseAmount / betAmount); - - return { - name: this.generateRandomName(), - amount: baseAmount, - game, - timestamp: new Date(), - isVIP: Math.random() > this.VIP_CHANCE, - betAmount, - multiplier, - }; - } - - private calculateBetAmount(): number { - return Math.floor(Math.random() * 100) + 10; - } - - private updateWinnersList(winner: Winner): void { - const currentWinners = this.winners.value; - const updatedWinners = [winner, ...currentWinners]; - - if (updatedWinners.length > this.MAX_RECENT_WINNERS) { - updatedWinners.pop(); - } - - this.winners.next(updatedWinners); - } - - private generateRandomName(): string { - const firstName = this.getRandomArrayElement(this.PLAYER_NAMES.FIRST); - const lastName = this.getRandomArrayElement(this.PLAYER_NAMES.LAST); - return `${firstName} ${lastName}`; - } - - private calculateNewPlayerCount(currentCount: number): number { - const change = Math.floor(Math.random() * this.ONLINE_PLAYERS_LIMITS.CHANGE_RANGE) - 5; - return Math.max( - this.ONLINE_PLAYERS_LIMITS.MIN, - Math.min(this.ONLINE_PLAYERS_LIMITS.MAX, currentCount + change) - ); - } - - private getRandomArrayElement(array: T[]): T { - return array[Math.floor(Math.random() * array.length)]; - } -} diff --git a/frontend/src/app/shared/components/animated-button/animated-button.component.ts b/frontend/src/app/shared/components/animated-button/animated-button.component.ts deleted file mode 100644 index 85c433e..0000000 --- a/frontend/src/app/shared/components/animated-button/animated-button.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - Component, - Input, - Output, - EventEmitter, - ElementRef, - ChangeDetectionStrategy, -} from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { AnimationService } from '../../../services/animation.service'; - -@Component({ - selector: 'app-animated-button', - standalone: true, - imports: [CommonModule], - template: ` - - `, - styles: [], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AnimatedButtonComponent { - @Input() variant: 'primary' | 'secondary' = 'primary'; - @Input() size: 'normal' | 'large' = 'normal'; - @Output() buttonClick = new EventEmitter(); - - constructor(private animationService: AnimationService) {} - - get buttonClass(): string { - const baseClass = - 'cursor-pointer relative group font-bold rounded-full transition-all duration-300 ease-out transform-gpu hover:scale-105 will-change-transform'; - const variantClass = - this.variant === 'primary' - ? 'bg-gradient-to-r from-emerald-500 to-emerald-400 text-black hover:shadow-xl hover:shadow-emerald-500/20' - : 'bg-white/10 text-white hover:bg-white/20'; - - return `${baseClass} ${variantClass}`; - } - - handleClick(event: MouseEvent): void { - const elementRef = new ElementRef(event.currentTarget); - this.animationService.animateButtonClick(elementRef); - this.buttonClick.emit(event); - } -} diff --git a/frontend/src/app/shared/components/game-card/game-card.component.ts b/frontend/src/app/shared/components/game-card/game-card.component.ts deleted file mode 100644 index 78dacac..0000000 --- a/frontend/src/app/shared/components/game-card/game-card.component.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { - Component, - Input, - Output, - EventEmitter, - ElementRef, - ChangeDetectionStrategy, -} from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { Game } from '../../../services/game.service'; -import { AnimatedButtonComponent } from '../animated-button/animated-button.component'; -import { AnimationService } from '../../../services/animation.service'; - -@Component({ - selector: 'app-game-card', - standalone: true, - imports: [CommonModule, AnimatedButtonComponent], - template: ` -
-
- -
-
-
-

{{ game.name }}

-
- - HOT 🔥 - - - {{ game.lastWinner }} won €{{ game.lastWin | number }} - -
-
-

{{ game.description }}

-
-
- - {{ game.winChance }}% Win Rate - - Max Win: €{{ game.maxWin | number }} -
-
- - Min: €{{ game.minBet }} | Max: €{{ game.maxBet }} - -
- Popularity: -
-
-
-
-
-
-
- - {{ feature }} - -
- PLAY NOW -
-
-
-
-
- `, - styles: [], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class GameCardComponent { - @Input() game!: Game; - @Output() play = new EventEmitter(); - - constructor(private animationService: AnimationService) {} - - onHover(event: MouseEvent): void { - const element = event.currentTarget as HTMLElement; - const elementRef = new ElementRef(element); - this.animationService.animateFloat(elementRef); - } - - onPlay(): void { - this.play.emit(); - } -} diff --git a/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts b/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts deleted file mode 100644 index 7e0c733..0000000 --- a/frontend/src/app/shared/components/winner-ticker/winner-ticker.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - Component, - Input, - ViewChild, - ElementRef, - AfterViewInit, - ChangeDetectionStrategy, -} from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { default as autoAnimate } from '@formkit/auto-animate'; -import { Winner } from '../../../services/winner.service'; - -@Component({ - selector: 'app-winner-ticker', - standalone: true, - imports: [CommonModule], - template: ` -
- - 🎰 - - {{ winner.name }} - {{ winner.isVIP ? '(VIP)' : '' }} - won €{{ winner.amount | number }} - ({{ winner.multiplier }}x) - - -
- `, - styles: [], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class WinnerTickerComponent implements AfterViewInit { - @Input() winners: Winner[] = []; - @ViewChild('tickerContainer') tickerContainer!: ElementRef; - - ngAfterViewInit(): void { - autoAnimate(this.tickerContainer.nativeElement); - } -} -- 2.47.2 From 61add61113ed2c2c16639a913217e39335168bc9 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 12 Feb 2025 11:26:48 +0100 Subject: [PATCH 09/22] feat: add navbar component to the application --- frontend/src/app/app.component.html | 1 + frontend/src/app/app.component.ts | 4 +- .../components/navbar/navbar.component.html | 42 +++++++++++++++++++ .../components/navbar/navbar.component.scss | 34 +++++++++++++++ .../components/navbar/navbar.component.ts | 18 ++++++++ frontend/tailwind.config.js | 28 +++++++++++++ 6 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 frontend/src/app/shared/components/navbar/navbar.component.html create mode 100644 frontend/src/app/shared/components/navbar/navbar.component.scss create mode 100644 frontend/src/app/shared/components/navbar/navbar.component.ts diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 0680b43..6659729 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1 +1,2 @@ + diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 7dea888..0a852aa 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -2,11 +2,11 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterOutlet } from '@angular/router'; import { KeycloakAngularModule } from 'keycloak-angular'; - +import { NavbarComponent } from './shared/components/navbar/navbar.component'; @Component({ selector: 'app-root', standalone: true, - imports: [CommonModule, RouterOutlet, KeycloakAngularModule], + imports: [CommonModule, RouterOutlet, KeycloakAngularModule, NavbarComponent], providers: [], templateUrl: './app.component.html', styleUrl: './app.component.css', diff --git a/frontend/src/app/shared/components/navbar/navbar.component.html b/frontend/src/app/shared/components/navbar/navbar.component.html new file mode 100644 index 0000000..cd772bd --- /dev/null +++ b/frontend/src/app/shared/components/navbar/navbar.component.html @@ -0,0 +1,42 @@ + \ No newline at end of file diff --git a/frontend/src/app/shared/components/navbar/navbar.component.scss b/frontend/src/app/shared/components/navbar/navbar.component.scss new file mode 100644 index 0000000..1f11eda --- /dev/null +++ b/frontend/src/app/shared/components/navbar/navbar.component.scss @@ -0,0 +1,34 @@ +:host { + display: block; +} + +.text-gold { + color: #FFD700; +} + +.bg-gold { + background-color: #FFD700; + &:hover { + background-color: #FFC000; + } +} + +.hover\:bg-gold-dark:hover { + background-color: #FFC000; +} + +.hover\:text-gold:hover { + color: #FFD700; +} + +nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; +} + +.transition-colors { + transition: all 0.2s ease-in-out; +} \ No newline at end of file diff --git a/frontend/src/app/shared/components/navbar/navbar.component.ts b/frontend/src/app/shared/components/navbar/navbar.component.ts new file mode 100644 index 0000000..e4eb7c2 --- /dev/null +++ b/frontend/src/app/shared/components/navbar/navbar.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-navbar', + templateUrl: './navbar.component.html', + styleUrls: ['./navbar.component.scss'], + standalone: true, + imports: [CommonModule, RouterModule] +}) +export class NavbarComponent { + isMenuOpen = false; + + toggleMenu() { + this.isMenuOpen = !this.isMenuOpen; + } +} \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 843fec3..87e76e4 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -5,6 +5,34 @@ module.exports = { ], theme: { extend: { + colors: { + // Core colors from the palette + 'primary': { + 900: '#000000', // #000000 + 800: '#05080a', // #05080a + 700: '#071d2a', // #071d2a + 600: '#0f212e', // #0f212e - dark navy + 500: '#1a2c38', // #1a2c38 + 400: '#213743', // #213743 + 300: '#2f4553', // #2f4553 + }, + 'accent': { + blue: '#1475e1', // Bright blue + green: '#00e701', // Bright green + }, + 'gray': { + 400: '#557086', // Mid gray + 300: '#b1bad3', // Light gray + 200: '#d5dceb', // Lighter gray + 100: '#ffffff', // White + }, + // Semantic colors + 'gold': { + DEFAULT: '#b1bad3', + light: '#d5dceb', + dark: '#557086', + }, + }, animation: { 'fadeIn': 'fadeIn 0.3s ease-out', 'backdropBlur': 'backdropBlur 0.4s ease-out', -- 2.47.2 From 21fa5a21e2620ec282efa9152c06d2ee445526d9 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 12 Feb 2025 11:28:59 +0100 Subject: [PATCH 10/22] chore: remove unused tailwind configuration file --- frontend/tailwind.config.js | 246 ------------------------------------ 1 file changed, 246 deletions(-) delete mode 100644 frontend/tailwind.config.js diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js deleted file mode 100644 index 87e76e4..0000000 --- a/frontend/tailwind.config.js +++ /dev/null @@ -1,246 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: [ - "./src/**/*.{html,ts}", - ], - theme: { - extend: { - colors: { - // Core colors from the palette - 'primary': { - 900: '#000000', // #000000 - 800: '#05080a', // #05080a - 700: '#071d2a', // #071d2a - 600: '#0f212e', // #0f212e - dark navy - 500: '#1a2c38', // #1a2c38 - 400: '#213743', // #213743 - 300: '#2f4553', // #2f4553 - }, - 'accent': { - blue: '#1475e1', // Bright blue - green: '#00e701', // Bright green - }, - 'gray': { - 400: '#557086', // Mid gray - 300: '#b1bad3', // Light gray - 200: '#d5dceb', // Lighter gray - 100: '#ffffff', // White - }, - // Semantic colors - 'gold': { - DEFAULT: '#b1bad3', - light: '#d5dceb', - dark: '#557086', - }, - }, - animation: { - 'fadeIn': 'fadeIn 0.3s ease-out', - 'backdropBlur': 'backdropBlur 0.4s ease-out', - 'modalSlideIn': 'modalSlideIn 0.5s cubic-bezier(0.16,1,0.3,1)', - 'slideDown': 'slideDown 0.6s ease-out', - 'slideUp': 'slideUp 0.6s ease-out', - 'scaleIn': 'scaleIn 0.8s cubic-bezier(0.16,1,0.3,1)', - 'spinAndBounce': 'spinAndBounce 3s ease-in-out infinite', - 'glow': 'glow 4s ease-in-out infinite', - 'marquee': 'marquee 30s linear infinite', - 'float': 'float 6s ease-in-out infinite', - 'tiltAndGlow': 'tiltAndGlow 3s ease-in-out infinite', - 'shimmer': 'shimmer 2s linear infinite', - 'morphBackground': 'morphBackground 10s ease-in-out infinite', - 'elasticScale': 'elasticScale 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55)', - 'bounceAndFade': 'bounceAndFade 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55)', - 'rotateAndScale': 'rotateAndScale 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55)', - 'pulseGlow': 'pulseGlow 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', - }, - keyframes: { - fadeIn: { - 'from': { opacity: '0' }, - 'to': { opacity: '1' } - }, - backdropBlur: { - 'from': { - 'backdrop-filter': 'blur(0px)', - 'background-color': 'rgba(0,0,0,0)' - }, - 'to': { - 'backdrop-filter': 'blur(16px)', - 'background-color': 'rgba(0,0,0,0.95)' - } - }, - modalSlideIn: { - 'from': { - opacity: '0', - transform: 'scale(0.95) translateY(10px)' - }, - 'to': { - opacity: '1', - transform: 'scale(1) translateY(0)' - } - }, - slideDown: { - 'from': { - opacity: '0', - transform: 'translateY(-20px)' - }, - 'to': { - opacity: '1', - transform: 'translateY(0)' - } - }, - slideUp: { - 'from': { - opacity: '0', - transform: 'translateY(20px)' - }, - 'to': { - opacity: '1', - transform: 'translateY(0)' - } - }, - scaleIn: { - 'from': { - opacity: '0', - transform: 'scale(0.9)' - }, - 'to': { - opacity: '1', - transform: 'scale(1)' - } - }, - spinAndBounce: { - '0%': { - transform: 'scale(1) rotate(0deg)' - }, - '50%': { - transform: 'scale(1.2) rotate(180deg)' - }, - '100%': { - transform: 'scale(1) rotate(360deg)' - } - }, - glow: { - '0%, 100%': { - opacity: '0.3', - transform: 'scale(1)' - }, - '50%': { - opacity: '0.5', - transform: 'scale(1.05)' - } - }, - marquee: { - '0%': { transform: 'translateX(0)' }, - '100%': { transform: 'translateX(-100%)' } - }, - float: { - '0%, 100%': { transform: 'translateY(0)' }, - '50%': { transform: 'translateY(-20px)' } - }, - tiltAndGlow: { - '0%, 100%': { - transform: 'perspective(1000px) rotateX(0deg) rotateY(0deg)', - 'box-shadow': '0 0 20px rgba(34,197,94,0.2)' - }, - '50%': { - transform: 'perspective(1000px) rotateX(2deg) rotateY(5deg)', - 'box-shadow': '0 0 40px rgba(34,197,94,0.4)' - } - }, - shimmer: { - '0%': { - 'background-position': '-1000px 0' - }, - '100%': { - 'background-position': '1000px 0' - } - }, - morphBackground: { - '0%, 100%': { - 'border-radius': '60% 40% 30% 70%/60% 30% 70% 40%' - }, - '50%': { - 'border-radius': '30% 60% 70% 40%/50% 60% 30% 60%' - } - }, - elasticScale: { - '0%': { - transform: 'scale(0)' - }, - '60%': { - transform: 'scale(1.1)' - }, - '100%': { - transform: 'scale(1)' - } - }, - bounceAndFade: { - '0%': { - transform: 'scale(0.3)', - opacity: '0' - }, - '50%': { - transform: 'scale(1.05)', - opacity: '0.8' - }, - '100%': { - transform: 'scale(1)', - opacity: '1' - } - }, - rotateAndScale: { - '0%': { - transform: 'rotate(-180deg) scale(0)' - }, - '100%': { - transform: 'rotate(0) scale(1)' - } - }, - pulseGlow: { - '0%, 100%': { - opacity: '1', - transform: 'scale(1)', - filter: 'brightness(1)' - }, - '50%': { - opacity: '0.8', - transform: 'scale(1.05)', - filter: 'brightness(1.2)' - } - } - }, - transitionDelay: { - '100': '100ms', - '200': '200ms', - '300': '300ms', - '400': '400ms', - '500': '500ms', - }, - transitionTimingFunction: { - 'bounce': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', - 'smooth': 'cubic-bezier(0.4, 0, 0.2, 1)', - 'elastic': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)', - 'spring': 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', - }, - backdropBlur: { - 'xs': '2px', - '4xl': '72px', - '5xl': '96px', - }, - backgroundImage: { - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', - 'shimmer': 'linear-gradient(90deg, transparent, rgba(255,255,255,0.08), transparent)', - } - }, - }, - plugins: [ - require('@tailwindcss/aspect-ratio'), - require('@tailwindcss/forms'), - require('@tailwindcss/typography'), - require('tailwindcss-animated'), - require('tailwindcss-gradients'), - require('tailwindcss-transforms'), - require('tailwindcss-filters'), - require('tailwind-scrollbar-hide'), - ], -} -- 2.47.2 From 70044fc3c726b929c77f3e27378b80108d51c2fc Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 12 Feb 2025 11:38:28 +0100 Subject: [PATCH 11/22] style(navbar): update navbar colors to use variables --- .../shared/components/navbar/navbar.component.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/shared/components/navbar/navbar.component.html b/frontend/src/app/shared/components/navbar/navbar.component.html index cd772bd..61447ad 100644 --- a/frontend/src/app/shared/components/navbar/navbar.component.html +++ b/frontend/src/app/shared/components/navbar/navbar.component.html @@ -1,4 +1,4 @@ - diff --git a/frontend/src/app/shared/components/navbar/navbar.component.ts b/frontend/src/app/shared/components/navbar/navbar.component.ts index 338201c..43053a3 100644 --- a/frontend/src/app/shared/components/navbar/navbar.component.ts +++ b/frontend/src/app/shared/components/navbar/navbar.component.ts @@ -6,7 +6,7 @@ import { CommonModule } from '@angular/common'; selector: 'app-navbar', templateUrl: './navbar.component.html', standalone: true, - imports: [CommonModule, RouterModule] + imports: [CommonModule, RouterModule], }) export class NavbarComponent { isMenuOpen = false; @@ -14,4 +14,4 @@ export class NavbarComponent { toggleMenu() { this.isMenuOpen = !this.isMenuOpen; } -} \ No newline at end of file +} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 12c08a1..eacf745 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -17,3 +17,4 @@ body { --mdc-dialog-supporting-text-color: #9ca3af !important; --mdc-dialog-container-shape: 6px !important; } + -- 2.47.2 From e5bd173be981e05c9f645a8427ef3791a4f7cc91 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 12 Feb 2025 12:51:55 +0100 Subject: [PATCH 15/22] feat(navbar): implement login/logout buttons based on state --- .../app/feature/landing/landing.component.ts | 1 + .../landing-page/landing-page.component.html | 1 + .../components/footer/footer.component.ts | 5 +-- .../components/navbar/navbar.component.html | 42 ++++++++++++++----- .../components/navbar/navbar.component.ts | 24 +++++++++-- 5 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 frontend/src/app/landing-page/landing-page.component.html diff --git a/frontend/src/app/feature/landing/landing.component.ts b/frontend/src/app/feature/landing/landing.component.ts index d2e28c0..417ac38 100644 --- a/frontend/src/app/feature/landing/landing.component.ts +++ b/frontend/src/app/feature/landing/landing.component.ts @@ -19,3 +19,4 @@ export class LandingComponent { this.keycloakService.login({ redirectUri: `${baseUrl}/home` }); } } +export class LandingPageComponent {} diff --git a/frontend/src/app/landing-page/landing-page.component.html b/frontend/src/app/landing-page/landing-page.component.html new file mode 100644 index 0000000..aa8bbd8 --- /dev/null +++ b/frontend/src/app/landing-page/landing-page.component.html @@ -0,0 +1 @@ + diff --git a/frontend/src/app/shared/components/footer/footer.component.ts b/frontend/src/app/shared/components/footer/footer.component.ts index 006a23b..1c3b309 100644 --- a/frontend/src/app/shared/components/footer/footer.component.ts +++ b/frontend/src/app/shared/components/footer/footer.component.ts @@ -1,5 +1,4 @@ -import { Component } from '@angular/core'; -import { RouterLink } from '@angular/router'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { faMoneyBillTransfer, faCreditCard, faWallet } from '@fortawesome/free-solid-svg-icons'; import { faPaypal, faGooglePay, faApplePay } from '@fortawesome/free-brands-svg-icons'; @@ -9,11 +8,11 @@ import { faPaypal, faGooglePay, faApplePay } from '@fortawesome/free-brands-svg- standalone: true, templateUrl: './footer.component.html', imports: [FontAwesomeModule], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FooterComponent { currentYear: number = new Date().getFullYear(); - // Payment method icons faPaypal = faPaypal; faCreditCard = faCreditCard; faMoneyBillTransfer = faMoneyBillTransfer; diff --git a/frontend/src/app/shared/components/navbar/navbar.component.html b/frontend/src/app/shared/components/navbar/navbar.component.html index fa990e6..b0abbbb 100644 --- a/frontend/src/app/shared/components/navbar/navbar.component.html +++ b/frontend/src/app/shared/components/navbar/navbar.component.html @@ -15,11 +15,22 @@
@@ -67,11 +78,22 @@ >Games
- + @if (!isLoggedIn) { + + } + @if (isLoggedIn) { + + }
diff --git a/frontend/src/app/shared/components/navbar/navbar.component.ts b/frontend/src/app/shared/components/navbar/navbar.component.ts index 43053a3..eb88f52 100644 --- a/frontend/src/app/shared/components/navbar/navbar.component.ts +++ b/frontend/src/app/shared/components/navbar/navbar.component.ts @@ -1,15 +1,33 @@ -import { Component } from '@angular/core'; +import { AsyncPipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { CommonModule } from '@angular/common'; +import { KeycloakService } from 'keycloak-angular'; @Component({ selector: 'app-navbar', templateUrl: './navbar.component.html', standalone: true, - imports: [CommonModule, RouterModule], + imports: [RouterModule, AsyncPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class NavbarComponent { isMenuOpen = false; + private keycloakService: KeycloakService = inject(KeycloakService); + + isLoggedIn = this.keycloakService.isLoggedIn(); + + login() { + try { + const baseUrl = window.location.origin; + this.keycloakService.login({ redirectUri: `${baseUrl}/home` }); + } catch (error) { + console.error('Login failed:', error); + } + } + + logout() { + this.keycloakService.logout(); + } toggleMenu() { this.isMenuOpen = !this.isMenuOpen; -- 2.47.2 From b2f4d9d0d85f063b9f604f1bef15b5100da27643 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 12 Feb 2025 14:06:19 +0100 Subject: [PATCH 16/22] feat: add navbar component to multiple pages --- frontend/src/app/app.component.html | 3 +-- frontend/src/app/feature/home/home.component.html | 12 +----------- frontend/src/app/feature/home/home.component.ts | 5 +++-- .../src/app/landing-page/landing-page.component.html | 1 + .../src/app/landing-page/landing-page.component.ts | 10 ++++++++++ 5 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 frontend/src/app/landing-page/landing-page.component.ts diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 67fc2d4..828546c 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1,7 +1,6 @@
-
-
+ \ No newline at end of file diff --git a/frontend/src/app/feature/home/home.component.html b/frontend/src/app/feature/home/home.component.html index b85ce29..27cec95 100644 --- a/frontend/src/app/feature/home/home.component.html +++ b/frontend/src/app/feature/home/home.component.html @@ -1,14 +1,4 @@ - +
diff --git a/frontend/src/app/feature/home/home.component.ts b/frontend/src/app/feature/home/home.component.ts index a4b2b8b..f179e2c 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -3,11 +3,12 @@ import { KeycloakService } from 'keycloak-angular'; import { MatDialog } from '@angular/material/dialog'; import { DepositComponent } from '../deposit/deposit.component'; +import { NavbarComponent } from '../../shared/components/navbar/navbar.component'; @Component({ selector: 'app-homepage', standalone: true, - imports: [], - templateUrl: './home.component.html', + imports: [NavbarComponent], + templateUrl: './homepage.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class HomeComponent { diff --git a/frontend/src/app/landing-page/landing-page.component.html b/frontend/src/app/landing-page/landing-page.component.html index aa8bbd8..4e7c12f 100644 --- a/frontend/src/app/landing-page/landing-page.component.html +++ b/frontend/src/app/landing-page/landing-page.component.html @@ -1 +1,2 @@ + diff --git a/frontend/src/app/landing-page/landing-page.component.ts b/frontend/src/app/landing-page/landing-page.component.ts new file mode 100644 index 0000000..826d86f --- /dev/null +++ b/frontend/src/app/landing-page/landing-page.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; +import { NavbarComponent } from '../shared/components/navbar/navbar.component'; + +@Component({ + selector: 'app-landing-page', + standalone: true, + imports: [NavbarComponent], + templateUrl: './landing-page.component.html', +}) +export class LandingPageComponent {} -- 2.47.2 From 8e986727ec849af9f7995b74bdb4af267d280dd1 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Thu, 13 Feb 2025 12:13:24 +0100 Subject: [PATCH 17/22] feat(landing-page): add welcome bonus and game carousel --- .../landing-page/landing-page.component.html | 127 +++++++++++++++++- .../landing-page/landing-page.component.ts | 51 ++++++- .../components/footer/footer.component.html | 6 +- .../components/navbar/navbar.component.ts | 3 +- frontend/src/styles.css | 33 ++++- 5 files changed, 208 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/landing-page/landing-page.component.html b/frontend/src/app/landing-page/landing-page.component.html index 4e7c12f..954abf9 100644 --- a/frontend/src/app/landing-page/landing-page.component.html +++ b/frontend/src/app/landing-page/landing-page.component.html @@ -1,2 +1,127 @@ - + +
+
+
+
+

+ Willkommensbonus +

+
+ 200% bis zu 500€ +
+

+ + 200 Freispiele +

+ + +
+ +
+

Beliebte Spiele

+
+
+
+
+
+
+

Slots

+

Klassische Spielautomaten

+ +
+
+
+
+

Plinko

+

Spannendes Geschicklichkeitsspiel

+ +
+
+ +
+ +
+
+
+

Poker

+

Texas Hold'em & mehr

+ +
+
+
+
+

Liars Dice

+

Würfelspiel mit Strategie

+ +
+
+ +
+
+
+ + + + +
+ +
+
+
+ +
+
+
50 Mio.€+
+
Ausgezahlt
+
+ +
+
10 Mio.+
+
Spiele
+
+ +
+
24/7
+
Support *
+
+
+
+
+
diff --git a/frontend/src/app/landing-page/landing-page.component.ts b/frontend/src/app/landing-page/landing-page.component.ts index 826d86f..1471a99 100644 --- a/frontend/src/app/landing-page/landing-page.component.ts +++ b/frontend/src/app/landing-page/landing-page.component.ts @@ -1,10 +1,55 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from '@angular/core'; import { NavbarComponent } from '../shared/components/navbar/navbar.component'; +import { NgFor } from '@angular/common'; @Component({ selector: 'app-landing-page', standalone: true, - imports: [NavbarComponent], + imports: [NavbarComponent, NgFor], templateUrl: './landing-page.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class LandingPageComponent {} +export class LandingPageComponent implements OnInit, OnDestroy { + currentSlide = 0; + private autoplayInterval: any; + + ngOnInit() { + this.startAutoplay(); + } + + ngOnDestroy() { + this.stopAutoplay(); + } + + prevSlide() { + this.currentSlide = this.currentSlide === 0 ? 1 : 0; + this.resetAutoplay(); + } + + nextSlide() { + this.currentSlide = this.currentSlide === 1 ? 0 : 1; + this.resetAutoplay(); + } + + goToSlide(index: number) { + this.currentSlide = index; + this.resetAutoplay(); + } + + private startAutoplay() { + this.autoplayInterval = setInterval(() => { + this.nextSlide(); + }, 5000); + } + + private stopAutoplay() { + if (this.autoplayInterval) { + clearInterval(this.autoplayInterval); + } + } + + private resetAutoplay() { + this.stopAutoplay(); + this.startAutoplay(); + } +} diff --git a/frontend/src/app/shared/components/footer/footer.component.html b/frontend/src/app/shared/components/footer/footer.component.html index c5709d3..d29c406 100644 --- a/frontend/src/app/shared/components/footer/footer.component.html +++ b/frontend/src/app/shared/components/footer/footer.component.html @@ -1,4 +1,4 @@ -