feat: implement animated buttons and winner ticker component
This commit is contained in:
parent
e6e7d65602
commit
50eb399fe2
@ -6,20 +6,7 @@
|
|||||||
[class.py-1.5]="!isScrolled"
|
[class.py-1.5]="!isScrolled"
|
||||||
>
|
>
|
||||||
<div class="container mx-auto px-4 overflow-hidden">
|
<div class="container mx-auto px-4 overflow-hidden">
|
||||||
<div #winnersMarquee class="flex items-center gap-12 animate-marquee will-change-transform">
|
<app-winner-ticker [winners]="(recentWinners$ | async) ?? []"></app-winner-ticker>
|
||||||
<span
|
|
||||||
*ngFor="let winner of recentWinners$ | async"
|
|
||||||
class="flex items-center gap-3 whitespace-nowrap"
|
|
||||||
>
|
|
||||||
<span class="text-yellow-300 text-lg animate-pulseGlow will-change-transform">🎰</span>
|
|
||||||
<span class="font-medium">
|
|
||||||
{{ winner.name }}
|
|
||||||
<span class="text-yellow-300 font-semibold">{{ winner.isVIP ? '(VIP)' : '' }}</span>
|
|
||||||
won <span class="text-yellow-300 font-semibold">€{{ winner.amount | number }}</span>
|
|
||||||
<span class="text-yellow-300">({{ winner.multiplier }}x)</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -60,15 +47,7 @@
|
|||||||
<span class="text-emerald-500 text-xs font-semibold">(78.9% Win Rate)</span>
|
<span class="text-emerald-500 text-xs font-semibold">(78.9% Win Rate)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<app-animated-button (buttonClick)="claimBonus()"> START PLAYING </app-animated-button>
|
||||||
(click)="claimBonus(); onButtonClick($event)"
|
|
||||||
class="relative group px-8 py-3 bg-gradient-to-r from-emerald-500 to-emerald-400 text-black font-bold rounded-full transition-all duration-300 ease-out transform-gpu hover:scale-105 hover:shadow-xl hover:shadow-emerald-500/20 will-change-transform"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 rounded-full bg-white/20 opacity-0 group-hover:opacity-100 transition-all duration-500 ease-in-out"
|
|
||||||
></div>
|
|
||||||
<span class="relative text-sm tracking-wide">START PLAYING</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -157,17 +136,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<button
|
<app-animated-button variant="primary" size="large" (buttonClick)="claimBonus()">
|
||||||
(click)="claimBonus(); onButtonClick($event)"
|
CLAIM YOUR €10,000 NOW
|
||||||
class="group relative px-12 py-5 bg-gradient-to-r from-emerald-500 to-emerald-400 rounded-full transition-all duration-300 transform hover:scale-105 hover:shadow-2xl hover:shadow-emerald-500/20"
|
</app-animated-button>
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 rounded-full bg-white/20 opacity-0 group-hover:opacity-100 transition-all duration-500"
|
|
||||||
></div>
|
|
||||||
<span class="relative text-2xl font-bold text-black tracking-wide"
|
|
||||||
>CLAIM YOUR €10,000 NOW</span
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center gap-12 mt-10">
|
<div class="flex justify-center gap-12 mt-10">
|
||||||
@ -190,25 +161,7 @@
|
|||||||
<div
|
<div
|
||||||
class="bg-black/40 backdrop-blur-xl rounded-xl p-4 mb-16 overflow-hidden border border-white/5"
|
class="bg-black/40 backdrop-blur-xl rounded-xl p-4 mb-16 overflow-hidden border border-white/5"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-8">
|
<app-winner-ticker [winners]="(recentWinners$ | async) ?? []"></app-winner-ticker>
|
||||||
<div class="flex-none animate-marquee">
|
|
||||||
<span *ngFor="let winner of recentWinners$ | async" class="mx-6 whitespace-nowrap">
|
|
||||||
<span
|
|
||||||
[class.animate-pulseGlow]="winner.isVIP"
|
|
||||||
[class.drop-shadow-[0_0_8px_rgba(234,179,8,0.3)]]="winner.isVIP"
|
|
||||||
class="text-white/80 font-medium"
|
|
||||||
>
|
|
||||||
🎰 {{ winner.name }}
|
|
||||||
<span class="text-yellow-400 font-semibold">{{
|
|
||||||
winner.isVIP ? '(VIP)' : ''
|
|
||||||
}}</span>
|
|
||||||
turned <span class="text-emerald-400">€{{ winner.betAmount }}</span> into
|
|
||||||
<span class="text-emerald-400 font-bold">€{{ winner.amount | number }}</span>
|
|
||||||
<span class="text-yellow-400">({{ winner.multiplier }}x)</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div #gamesGrid class="mb-20">
|
<div #gamesGrid class="mb-20">
|
||||||
@ -220,79 +173,12 @@
|
|||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid md:grid-cols-3 gap-8">
|
<div class="grid md:grid-cols-3 gap-8">
|
||||||
<div
|
<app-game-card
|
||||||
*ngFor="let game of games$ | async"
|
*ngFor="let game of games$ | async"
|
||||||
class="game-card group relative overflow-hidden rounded-2xl cursor-pointer bg-black/40 backdrop-blur-xl border border-white/5 transition-all duration-500 transform hover:scale-[1.02] hover:shadow-2xl hover:shadow-emerald-500/10"
|
[game]="game"
|
||||||
(mouseenter)="onGameCardHover($event)"
|
(play)="playNow(game.id)"
|
||||||
>
|
>
|
||||||
<div
|
</app-game-card>
|
||||||
class="absolute inset-0 bg-gradient-to-br from-emerald-500/10 to-emerald-400/10 opacity-0 group-hover:opacity-100 transition-opacity duration-500"
|
|
||||||
></div>
|
|
||||||
<img
|
|
||||||
[src]="game.imageUrl"
|
|
||||||
[alt]="game.name"
|
|
||||||
class="w-full h-64 object-cover transition-transform duration-700 group-hover:scale-110"
|
|
||||||
/>
|
|
||||||
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/80 to-transparent">
|
|
||||||
<div class="absolute bottom-0 left-0 right-0 p-8">
|
|
||||||
<div class="flex justify-between items-start mb-4">
|
|
||||||
<h3 class="text-2xl font-bold text-white tracking-tight">{{ game.name }}</h3>
|
|
||||||
<div class="flex flex-col items-end gap-2">
|
|
||||||
<span
|
|
||||||
class="bg-emerald-500/20 text-emerald-400 text-sm px-3 py-1 rounded-full font-medium animate-pulseGlow"
|
|
||||||
>HOT 🔥</span
|
|
||||||
>
|
|
||||||
<span class="text-yellow-400 text-sm"
|
|
||||||
>{{ game.lastWinner }} won €{{ game.lastWin | number }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="text-white/60 mb-6 font-medium">{{ game.description }}</p>
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span
|
|
||||||
class="text-emerald-400 font-bold animate-pulseGlow drop-shadow-[0_0_8px_rgba(34,197,94,0.2)]"
|
|
||||||
>
|
|
||||||
{{ game.winChance }}% Win Rate
|
|
||||||
</span>
|
|
||||||
<span class="text-yellow-400 font-medium"
|
|
||||||
>Max Win: €{{ game.maxWin | number }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<span class="text-sm text-white/60"
|
|
||||||
>Min: €{{ game.minBet }} | Max: €{{ game.maxBet }}</span
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-xs text-white/60">Popularity:</span>
|
|
||||||
<div class="w-24 h-1.5 bg-white/5 rounded-full overflow-hidden">
|
|
||||||
<div
|
|
||||||
class="h-full bg-gradient-to-r from-emerald-500 to-emerald-400 transition-all duration-500"
|
|
||||||
[style.width]="game.popularity + '%'"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between items-center pt-2">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<span
|
|
||||||
*ngFor="let feature of game.features"
|
|
||||||
class="text-xs px-3 py-1 rounded-full bg-white/5 text-white/60 font-medium"
|
|
||||||
>
|
|
||||||
{{ feature }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
(click)="playNow(game.id); onButtonClick($event)"
|
|
||||||
class="px-6 py-2.5 bg-gradient-to-r from-emerald-500 to-emerald-400 text-black font-bold rounded-full transition-all duration-300 transform hover:scale-105 hover:shadow-lg hover:shadow-emerald-500/20 text-sm tracking-wide"
|
|
||||||
>
|
|
||||||
PLAY NOW
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -396,17 +282,9 @@
|
|||||||
>
|
>
|
||||||
Maybe later
|
Maybe later
|
||||||
</button>
|
</button>
|
||||||
<button
|
<app-animated-button (buttonClick)="claimBonus()">
|
||||||
(click)="claimBonus(); onButtonClick($event)"
|
{{ popup.cta }}
|
||||||
class="group px-8 py-3 bg-gradient-to-r from-emerald-500 to-emerald-400 text-black font-bold rounded-full transition-all duration-500 transform hover:scale-105 hover:shadow-2xl hover:shadow-emerald-500/20 hover:translate-x-0.5 text-sm tracking-wide"
|
</app-animated-button>
|
||||||
>
|
|
||||||
<span class="relative z-10 group-hover:tracking-wider transition-all duration-300">{{
|
|
||||||
popup.cta
|
|
||||||
}}</span>
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 rounded-full bg-white/20 opacity-0 group-hover:opacity-100 transition-all duration-500 scale-105 group-hover:scale-100"
|
|
||||||
></div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
*ngIf="popup.expires"
|
*ngIf="popup.expires"
|
||||||
@ -463,17 +341,13 @@
|
|||||||
Hot streak detected - Increased win probability activated!
|
Hot streak detected - Increased win probability activated!
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<app-animated-button
|
||||||
(click)="playNow('mega-fortune'); onButtonClick($event)"
|
variant="primary"
|
||||||
class="group px-8 py-3 bg-gradient-to-r from-emerald-500 to-emerald-400 text-black font-bold rounded-full transition-all duration-500 transform hover:scale-105 hover:shadow-2xl hover:shadow-emerald-500/20 text-sm tracking-wide animate-slideUp"
|
size="normal"
|
||||||
|
(buttonClick)="playNow('mega-fortune')"
|
||||||
>
|
>
|
||||||
<span class="relative z-10 group-hover:tracking-wider transition-all duration-300"
|
SPIN AGAIN
|
||||||
>SPIN AGAIN</span
|
</app-animated-button>
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="absolute inset-0 rounded-full bg-white/20 opacity-0 group-hover:opacity-100 transition-all duration-500 scale-105 group-hover:scale-100"
|
|
||||||
></div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,10 +22,14 @@ import { WinnerService } from '../services/winner.service';
|
|||||||
import { JackpotService } from '../services/jackpot.service';
|
import { JackpotService } from '../services/jackpot.service';
|
||||||
import { AnimationService } from '../services/animation.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({
|
@Component({
|
||||||
selector: 'app-landing',
|
selector: 'app-landing',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule],
|
imports: [CommonModule, AnimatedButtonComponent, WinnerTickerComponent, GameCardComponent],
|
||||||
templateUrl: './landing.component.html',
|
templateUrl: './landing.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
animations: [
|
animations: [
|
||||||
|
@ -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: `
|
||||||
|
<button
|
||||||
|
(click)="handleClick($event)"
|
||||||
|
[class]="buttonClass"
|
||||||
|
[ngClass]="size === 'large' ? 'px-12 py-5 text-2xl' : 'px-8 py-3 text-sm'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 rounded-full bg-white/20 opacity-0 group-hover:opacity-100 transition-all duration-500 ease-in-out"
|
||||||
|
></div>
|
||||||
|
<span class="relative z-10 group-hover:tracking-wider transition-all duration-300">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
`,
|
||||||
|
styles: [],
|
||||||
|
})
|
||||||
|
export class AnimatedButtonComponent {
|
||||||
|
@Input() variant: 'primary' | 'secondary' = 'primary';
|
||||||
|
@Input() size: 'normal' | 'large' = 'normal';
|
||||||
|
@Output() buttonClick = new EventEmitter<MouseEvent>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -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: `
|
||||||
|
<div
|
||||||
|
class="game-card group relative overflow-hidden rounded-2xl cursor-pointer bg-black/40 backdrop-blur-xl border border-white/5
|
||||||
|
transition-all duration-500 transform hover:scale-[1.02] hover:shadow-2xl hover:shadow-emerald-500/10"
|
||||||
|
(mouseenter)="onHover($event)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 bg-gradient-to-br from-emerald-500/10 to-emerald-400/10 opacity-0
|
||||||
|
group-hover:opacity-100 transition-opacity duration-500"
|
||||||
|
></div>
|
||||||
|
<img
|
||||||
|
[src]="game.imageUrl"
|
||||||
|
[alt]="game.name"
|
||||||
|
class="w-full h-64 object-cover transition-transform duration-700 group-hover:scale-110"
|
||||||
|
/>
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/80 to-transparent">
|
||||||
|
<div class="absolute bottom-0 left-0 right-0 p-8">
|
||||||
|
<div class="flex justify-between items-start mb-4">
|
||||||
|
<h3 class="text-2xl font-bold text-white tracking-tight">{{ game.name }}</h3>
|
||||||
|
<div class="flex flex-col items-end gap-2">
|
||||||
|
<span
|
||||||
|
class="bg-emerald-500/20 text-emerald-400 text-sm px-3 py-1 rounded-full font-medium animate-pulseGlow"
|
||||||
|
>
|
||||||
|
HOT 🔥
|
||||||
|
</span>
|
||||||
|
<span class="text-yellow-400 text-sm">
|
||||||
|
{{ game.lastWinner }} won €{{ game.lastWin | number }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-white/60 mb-6 font-medium">{{ game.description }}</p>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span
|
||||||
|
class="text-emerald-400 font-bold animate-pulseGlow drop-shadow-[0_0_8px_rgba(34,197,94,0.2)]"
|
||||||
|
>
|
||||||
|
{{ game.winChance }}% Win Rate
|
||||||
|
</span>
|
||||||
|
<span class="text-yellow-400 font-medium">Max Win: €{{ game.maxWin | number }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-sm text-white/60">
|
||||||
|
Min: €{{ game.minBet }} | Max: €{{ game.maxBet }}
|
||||||
|
</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-xs text-white/60">Popularity:</span>
|
||||||
|
<div class="w-24 h-1.5 bg-white/5 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
class="h-full bg-gradient-to-r from-emerald-500 to-emerald-400 transition-all duration-500"
|
||||||
|
[style.width]="game.popularity + '%'"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center pt-2">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span
|
||||||
|
*ngFor="let feature of game.features"
|
||||||
|
class="text-xs px-3 py-1 rounded-full bg-white/5 text-white/60 font-medium"
|
||||||
|
>
|
||||||
|
{{ feature }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<app-animated-button (buttonClick)="onPlay()"> PLAY NOW </app-animated-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
styles: [],
|
||||||
|
})
|
||||||
|
export class GameCardComponent {
|
||||||
|
@Input() game!: Game;
|
||||||
|
@Output() play = new EventEmitter<void>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -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: `
|
||||||
|
<div class="flex items-center gap-12 animate-marquee will-change-transform" #tickerContainer>
|
||||||
|
<span *ngFor="let winner of winners" class="flex items-center gap-3 whitespace-nowrap">
|
||||||
|
<span class="text-yellow-300 text-lg animate-pulseGlow will-change-transform">🎰</span>
|
||||||
|
<span class="font-medium">
|
||||||
|
{{ winner.name }}
|
||||||
|
<span class="text-yellow-300 font-semibold">{{ winner.isVIP ? '(VIP)' : '' }}</span>
|
||||||
|
won <span class="text-yellow-300 font-semibold">€{{ winner.amount | number }}</span>
|
||||||
|
<span class="text-yellow-300">({{ winner.multiplier }}x)</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
styles: [],
|
||||||
|
})
|
||||||
|
export class WinnerTickerComponent implements AfterViewInit {
|
||||||
|
@Input() winners: Winner[] = [];
|
||||||
|
@ViewChild('tickerContainer') tickerContainer!: ElementRef;
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
autoAnimate(this.tickerContainer.nativeElement);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user