From d29fc10cc87d4925fab179eb6c9b8c58d5e38434 Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Wed, 7 May 2025 14:54:50 +0200 Subject: [PATCH] refactor: remove unused imports and clean up code --- .../lootbox-opening.component.css | 146 ++++++++--- .../lootbox-opening.component.html | 247 +++++++++--------- .../lootbox-opening.component.ts | 154 ++++++----- .../lootbox-selection.component.html | 2 +- 4 files changed, 312 insertions(+), 237 deletions(-) diff --git a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.css b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.css index 28fd570..c4f70f7 100644 --- a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.css +++ b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.css @@ -25,13 +25,14 @@ body { /* Loader animation */ .loader { - border: 4px solid rgba(255, 255, 255, 0.2); + border: 4px solid rgba(255, 255, 255, 0.3); border-radius: 50%; - border-top: 4px solid #facc15; + border-top: 4px solid #fff; width: 40px; height: 40px; animation: spin 1s linear infinite; } + @keyframes spin { 0% { transform: rotate(0deg); @@ -115,23 +116,20 @@ body { justify-content: center; align-items: center; gap: 8px; - padding: 5px 0px; /* Remove horizontal padding */ + padding: 5px 0; height: 100%; width: 100%; - animation: slide-in 10s cubic-bezier(0.05, 0.82, 0.17, 1) forwards; - transform: translateX(3000%); /* Extremely far initial position for 200 items */ - position: relative; /* Ensure positioning context */ + animation: slide-in 10s cubic-bezier(0.33, 0.9, 0.3, 1) forwards; + transform: translateX(4500px); + position: relative; } @keyframes slide-in { 0% { - transform: translateX(2000%); - } - 90% { - transform: translateX(-16%); /* Single overshoot for dramatic effect */ + transform: translateX(4500px); } 100% { - transform: translateX(-8%); /* Centered position */ + transform: translateX(-37.5px); } } @@ -140,6 +138,8 @@ body { padding: 2px; animation: item-flash 0.3s ease-out forwards; animation-play-state: paused; + width: 69px; + flex-shrink: 0; } @keyframes item-flash { @@ -163,7 +163,6 @@ body { flex-direction: column; align-items: center; justify-content: center; - width: 65px; height: 100%; } @@ -172,6 +171,16 @@ body { animation: highlight-winner 1s ease-out 10s forwards; } +.winning-prize { + border: 2px solid transparent; + transition: all 0.5s ease; +} + +.winning-prize.highlight { + border-color: #fff !important; + box-shadow: 0 0 8px rgba(255, 255, 255, 0.6) !important; +} + /* Specific ID for the winning item to ensure it's visible */ #winning-item { z-index: 5; /* Higher than indicator */ @@ -183,44 +192,44 @@ body { filter: brightness(1); } 10% { - transform: scale(1.3); - filter: brightness(2); + transform: scale(1.1); + filter: brightness(1.5); } 20% { - transform: scale(1.2); - filter: brightness(1.8); + transform: scale(1.05); + filter: brightness(1.3); } 30% { - transform: scale(1.25); - filter: brightness(2); + transform: scale(1.1); + filter: brightness(1.5); } 40% { - transform: scale(1.2); - filter: brightness(1.8); + transform: scale(1.05); + filter: brightness(1.3); } 50% { - transform: scale(1.25); - filter: brightness(2); + transform: scale(1.1); + filter: brightness(1.5); } 60% { - transform: scale(1.2); - filter: brightness(1.8); + transform: scale(1.05); + filter: brightness(1.3); } 70% { - transform: scale(1.25); - filter: brightness(2); + transform: scale(1.1); + filter: brightness(1.5); } 80% { - transform: scale(1.2); - filter: brightness(1.8); + transform: scale(1.05); + filter: brightness(1.3); } 90% { - transform: scale(1.25); - filter: brightness(2); + transform: scale(1.1); + filter: brightness(1.5); } 100% { - transform: scale(1.2); - filter: brightness(1.5); + transform: scale(1.05); + filter: brightness(1.3); } } @@ -234,3 +243,76 @@ body { font-size: 0.75rem; opacity: 0.7; } + +/* Prize animation */ +.prize-reel { + animation: slide-prizes 10s cubic-bezier(0.05, 0.82, 0.17, 1) forwards; +} + +@keyframes slide-prizes { + 0% { + transform: translateX(800px); + } + 85% { + transform: translateX(-120px); /* Small overshoot */ + } + 92% { + transform: translateX(-90px); /* Bounce back */ + } + 100% { + transform: translateX(-100px); /* Final position centered */ + } +} + +.animate-item-flash { + animation: item-flash 0.5s ease-out alternate infinite; +} + +.highlight { + animation: highlight-winner 1s ease-out forwards; +} + +/* Reward rarity classes */ +.text-common { + color: #ffffff; +} + +.text-uncommon { + color: #4ade80; +} + +.text-rare { + color: #60a5fa; +} + +.text-epic { + color: #a78bfa; +} + +.text-legendary { + color: #facc15; +} + +.text-mythic { + color: #f87171; +} + +.text-emerald { + color: #10b981; +} + +.text-accent-red { + color: #ef4444; +} + +.animation-fade { + opacity: 0; + transform: translateY(10px); + transition: opacity 0.5s ease-out, transform 0.5s ease-out; + transition-delay: 0.5s; +} + +.animation-fade.visible { + opacity: 1; + transform: translateY(0); +} diff --git a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.html b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.html index c85802d..531d1a0 100644 --- a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.html +++ b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.html @@ -1,136 +1,133 @@ -
-
-
-
Lade Lootbox...
+ +
+

Lootbox Öffnen

+ +
+
- - -

{{ lootbox.name }}

-
Preis: {{ lootbox.price | currency: 'EUR' }}
- - -
-

Mögliche Gewinne:

-
-
-
- {{ reward.value | currency: 'EUR' }} -
-
- Chance: {{ reward.probability * 100 | number: '1.0-0' }}% + +
+ {{ error }} +
+ +
+
+
+
+ +
+ {{ lootbox.price | currency:'EUR' }}
-
-
- - -
- -
- - -
-
-
Öffne Lootbox...
-
- - -
- -
-
Dein Gewinn:
-
- {{ wonReward?.value | currency: 'EUR' }} -
-
- - - - -
- -
- - -
-
- -
- -
-
-
{{ reward.value | currency: 'EUR' }}
-
{{ reward.probability * 100 | number: '1.0-0' }}%
+ +
+

{{ lootbox.name }}

+ +
+ +
+ +
+
+
Öffne Lootbox...
+
+ +
+
+

Dein Gewinn:

+
+ {{ wonReward?.value | currency: 'EUR' }}
- - -
+ +
+
+ +
+
+
+
+
+ {{ reward.value | currency: 'EUR' }} +
+
+ {{ reward.probability * 100 | number: '1.0-0' }}% +
+
+
+
+
+
+ +
+ + +
+
+
+ +
+
+

Fairness garantiert - Alle Ergebnisse werden transparent berechnet.

- - -
- - +
+ +
+
+

Mögliche Gewinne:

+
    +
  • + {{ reward.value | currency:'EUR' }} + {{ reward.probability * 100 | number: '1.0-0' }}% +
  • +
+ +
+

Gewinn-Details:

+
+
+ Kosten: + {{ lootbox.price | currency: 'EUR' }} +
+
+ Gewinn: + {{ wonReward.value | currency: 'EUR' }} +
+
+ Profit: + + {{ (wonReward.value - (lootbox.price || 0)) | currency: 'EUR' }} + +
+
+
- -
+
+
\ No newline at end of file diff --git a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts index 39a23a9..ca7c3ca 100644 --- a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts +++ b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts @@ -5,15 +5,6 @@ import { LootboxService } from '../services/lootbox.service'; import { LootBox, Reward } from 'app/model/LootBox'; import { NavbarComponent } from '@shared/components/navbar/navbar.component'; -function shuffle(array: T[]): T[] { - const arr = array.slice(); - for (let i = arr.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [arr[i], arr[j]] = [arr[j], arr[i]]; - } - return arr; -} - @Component({ selector: 'app-lootbox-opening', standalone: true, @@ -25,12 +16,11 @@ export default class LootboxOpeningComponent { lootbox: LootBox | null = null; isLoading = true; error = ''; - - // UI State isOpening = false; isOpen = false; wonReward: Reward | null = null; prizeList: Reward[] = []; + animationCompleted = false; constructor( private route: ActivatedRoute, @@ -38,12 +28,17 @@ export default class LootboxOpeningComponent { private lootboxService: LootboxService, private cdr: ChangeDetectorRef ) { + this.loadLootbox(); + } + + private loadLootbox(): void { const idParam = this.route.snapshot.paramMap.get('id'); if (!idParam) { this.error = 'Invalid lootbox ID'; this.isLoading = false; return; } + const lootboxId = parseInt(idParam, 10); this.lootboxService.getAllLootBoxes().subscribe({ next: (lootboxes) => { @@ -59,88 +54,86 @@ export default class LootboxOpeningComponent { }); } - openLootbox() { + openLootbox(): void { if (!this.lootbox || this.isOpening) return; - this.isOpening = true; - this.isOpen = false; - this.wonReward = null; - this.prizeList = []; // Clear previous prizes - this.cdr.detectChanges(); - - // Short delay to ensure animation plays from the beginning + + this.resetState(true); + setTimeout(() => { this.lootboxService.purchaseLootBox(this.lootbox!.id).subscribe({ - next: (reward) => { - this.wonReward = reward; - this.generateCasePrizes(reward); - this.isOpening = false; - this.isOpen = true; - this.cdr.detectChanges(); - }, - error: () => { - // Fallback if API fails - const rewards = this.lootbox!.rewards; - const fallback = rewards[Math.floor(Math.random() * rewards.length)]; - this.wonReward = fallback; - this.generateCasePrizes(fallback); - this.isOpening = false; - this.isOpen = true; - this.cdr.detectChanges(); - }, + next: this.handleRewardSuccess.bind(this), + error: this.handleRewardError.bind(this), }); }, 100); } - generateCasePrizes(wonReward: Reward) { + private handleRewardSuccess(reward: Reward): void { + this.wonReward = reward; + this.generateCasePrizes(reward); + this.isOpening = false; + this.isOpen = true; + this.cdr.detectChanges(); + } + + private handleRewardError(): void { + if (!this.lootbox) return; + + const rewards = this.lootbox.rewards; + const fallback = rewards[Math.floor(Math.random() * rewards.length)]; + this.handleRewardSuccess(fallback); + } + + private resetState(isOpening = false): void { + this.isOpening = isOpening; + this.isOpen = false; + this.wonReward = null; + this.prizeList = []; + this.animationCompleted = false; + this.cdr.detectChanges(); + } + + generateCasePrizes(wonReward: Reward): void { if (!this.lootbox) return; - // Create a case opening display with an extremely large number of prizes for a massive scrolling animation - const prizeCount = 200; // Set to 200 for an extremely long scrolling animation - const winningPosition = Math.floor(prizeCount / 2); // Position of the winning prize (middle) - - // Get possible rewards from the lootbox + const prizeCount = 120; + const winningPosition = Math.floor(prizeCount / 2); const possibleRewards = this.lootbox.rewards; - - // Generate an array of random rewards - let items: Reward[] = []; + const items: Reward[] = []; + for (let i = 0; i < prizeCount; i++) { - // Special handling for the winning position if (i === winningPosition) { - items.push(wonReward); + items.push({...wonReward}); } else { - // For all other positions, choose a random reward - // Weight rarer items to appear less frequently - const randomReward = this.getWeightedRandomReward(possibleRewards); - items.push(randomReward); + items.push(this.getWeightedRandomReward(possibleRewards)); } } this.prizeList = items; + + setTimeout(() => { + this.animationCompleted = true; + this.cdr.detectChanges(); + }, 10000); } getWeightedRandomReward(rewards: Reward[]): Reward { - // Create a weighted distribution based on probabilities const totalProbability = rewards.reduce((sum, reward) => sum + reward.probability, 0); const randomValue = Math.random() * totalProbability; - let cumulativeProbability = 0; + for (const reward of rewards) { cumulativeProbability += reward.probability; if (randomValue <= cumulativeProbability) { - return { ...reward }; // Return a copy of the reward + return { ...reward }; } } - - // Fallback, should never reach here + return { ...rewards[0] }; } - openAgain() { - this.isOpening = false; - this.isOpen = false; - this.wonReward = null; - this.prizeList = []; - this.cdr.detectChanges(); + openAgain(): void { + this.resetState(); + this.openLootbox(); } getBoxImage(id: number): string { @@ -152,25 +145,28 @@ export default class LootboxOpeningComponent { } isWonReward(reward: Reward): boolean { - if (!this.wonReward) { - return false; - } - - return ( - reward.id === this.wonReward.id && - reward.value === this.wonReward.value && - reward.probability === this.wonReward.probability - ); + if (!this.wonReward || !this.prizeList.length) return false; + + const middleIndex = Math.floor(this.prizeList.length / 2); + return this.prizeList.indexOf(reward) === middleIndex; } - - // Calculate the center position for better alignment - getCenterOffset(): string { - return '0px'; // No additional offset - using animation transform instead + + getRewardRarityClass(reward: Reward): string { + if (!reward) return 'text-common'; + + const probability = reward.probability; + + if (probability < 0.01) return 'text-mythic'; + if (probability < 0.05) return 'text-legendary'; + if (probability < 0.10) return 'text-epic'; + if (probability < 0.20) return 'text-rare'; + if (probability < 0.40) return 'text-uncommon'; + return 'text-common'; } - - // Check if item is at center position (100th item) - isCenterItem(index: number): boolean { - return index === Math.floor(200 / 2); // Center item at index 100 (0-indexed) + + getRewardClass(): string { + if (!this.wonReward || !this.lootbox) return ''; + return this.wonReward.value > (this.lootbox.price || 0) ? 'text-emerald' : 'text-accent-red'; } } diff --git a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.html b/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.html index 070e084..a4293fd 100644 --- a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.html +++ b/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.html @@ -2,7 +2,7 @@

Lootboxen

-
isLoading: {{ isLoading }} | error: {{ error }} | lootboxes: {{ lootboxes?.length }}
+
isLoading: {{ isLoading }} | error: {{ error }} | lootboxes: {{ lootboxes.length }}