feat(dice): enhance game UI and add sound effects
All checks were successful
CI / Get Changed Files (pull_request) Successful in 8s
CI / Checkstyle Main (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Has been skipped
CI / oxlint (pull_request) Successful in 18s
CI / eslint (pull_request) Successful in 25s
CI / prettier (pull_request) Successful in 25s
CI / Docker frontend validation (pull_request) Successful in 45s
CI / test-build (pull_request) Successful in 45s
All checks were successful
CI / Get Changed Files (pull_request) Successful in 8s
CI / Checkstyle Main (pull_request) Has been skipped
CI / Docker backend validation (pull_request) Has been skipped
CI / oxlint (pull_request) Successful in 18s
CI / eslint (pull_request) Successful in 25s
CI / prettier (pull_request) Successful in 25s
CI / Docker frontend validation (pull_request) Successful in 45s
CI / test-build (pull_request) Successful in 45s
This commit is contained in:
parent
c9632d6b26
commit
1849500d74
4 changed files with 374 additions and 108 deletions
|
@ -1,121 +1,305 @@
|
|||
<div class="container mx-auto px-4 py-8 space-y-8">
|
||||
<h1 class="text-3xl font-bold text-white mb-6">Dice Game</h1>
|
||||
<div class="container mx-auto px-4 py-6 space-y-8">
|
||||
<h1 class="text-3xl font-bold text-white mb-6">Dice</h1>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||
<div class="lg:col-span-3 space-y-6 flex flex-col gap-4">
|
||||
<form [formGroup]="diceForm">
|
||||
<div class="card p-6">
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h3 class="text-white font-semibold">
|
||||
Zielwert:
|
||||
<span class="text-white">{{
|
||||
diceForm.get('targetValue')?.value | number: '1.0-2'
|
||||
}}</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
<div class="lg:col-span-1 card p-8 space-y-6">
|
||||
<form [formGroup]="diceForm" (ngSubmit)="roll()" class="space-y-6">
|
||||
<div class="controls space-y-4">
|
||||
<div>
|
||||
<label for="betAmount" class="block text-text-secondary mb-2">Bet Amount:</label>
|
||||
<div class="relative py-4">
|
||||
<div class="flex justify-between text-xs text-text-secondary px-1 mb-2">
|
||||
<span>0</span>
|
||||
<span>25</span>
|
||||
<span>50</span>
|
||||
<span>75</span>
|
||||
<span>100</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes fade-out {
|
||||
from {
|
||||
opacity: 0.4;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.result-marker {
|
||||
transition: left 0.8s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
||||
}
|
||||
|
||||
.win-display {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="relative h-12 bg-deep-blue-dark rounded-xl overflow-hidden shadow-inner">
|
||||
<div
|
||||
class="absolute top-0 left-0 w-full h-full transition-all duration-300 ease-in-out"
|
||||
[style.background]="getTrackGradient()"
|
||||
></div>
|
||||
|
||||
<input
|
||||
id="targetValue"
|
||||
type="range"
|
||||
formControlName="targetValue"
|
||||
min="1"
|
||||
max="99"
|
||||
step="1"
|
||||
class="w-full h-full absolute top-0 left-0 opacity-0 cursor-pointer"
|
||||
[appDragSound]="diceForm.get('targetValue')"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="absolute top-1/2 -translate-y-1/2 w-10 h-10 bg-white rounded-full flex items-center justify-center shadow-lg pointer-events-none border-2 ease-in-out"
|
||||
[ngClass]="{
|
||||
'border-emerald': diceForm.get('rollOver')?.value,
|
||||
'border-accent-red': !diceForm.get('rollOver')?.value,
|
||||
}"
|
||||
[style.left]="'calc(' + (diceForm.get('targetValue')?.value ?? 50) + '% - 20px)'"
|
||||
>
|
||||
<div
|
||||
class="absolute -top-12 left-1/2 -translate-x-1/2 bg-white text-deep-blue-contrast px-3 py-1 rounded-md text-sm font-bold shadow transition-all duration-300 ease-in-out win-display"
|
||||
>
|
||||
{{ potentialWin() | currency: 'EUR' : 'symbol' : '1.2-2' }}
|
||||
</div>
|
||||
|
||||
<span class="text-deep-blue-contrast text-sm font-extrabold select-none">
|
||||
{{ diceForm.get('rollOver')?.value ? '>' : '<' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="hidden">
|
||||
<div class="relative flex items-center justify-center">
|
||||
<svg width="60" height="60" viewBox="0 0 100 100" class="filter drop-shadow-md">
|
||||
<polygon points="50,0 100,25 100,75 50,100 0,75 0,25" fill="white" />
|
||||
</svg>
|
||||
<span
|
||||
class="absolute text-deep-blue-contrast font-bold"
|
||||
style="font-size: 14px"
|
||||
>
|
||||
{{ potentialWin() | currency: 'EUR' : 'symbol' : '1.2-2' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (rolledValue() !== null) {
|
||||
<div
|
||||
class="absolute top-0 h-full z-20 result-marker"
|
||||
[style.left]="rolledValue() + '%'"
|
||||
style="transition: left 0.8s cubic-bezier(0.68, -0.55, 0.27, 1.55)"
|
||||
>
|
||||
<div
|
||||
class="h-full w-2 pointer-events-none animate-pulse"
|
||||
[ngClass]="{
|
||||
'bg-emerald': win(),
|
||||
'bg-accent-red': !win(),
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="relative h-1 mt-1">
|
||||
@for (i of [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; track i) {
|
||||
<div
|
||||
class="absolute top-0 w-0.5 h-1 bg-text-tertiary"
|
||||
[style.left]="i + '%'"
|
||||
></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (
|
||||
hasError('targetValue', 'required') ||
|
||||
hasError('targetValue', 'min') ||
|
||||
hasError('targetValue', 'max')
|
||||
) {
|
||||
<div class="p-2 bg-accent-red/10 border border-accent-red/20 rounded-lg">
|
||||
@if (hasError('targetValue', 'required')) {
|
||||
<span class="text-accent-red text-sm block">Zielwert ist erforderlich</span>
|
||||
}
|
||||
@if (hasError('targetValue', 'min')) {
|
||||
<span class="text-accent-red text-sm block">Zielwert muss mindestens 1 sein</span>
|
||||
}
|
||||
@if (hasError('targetValue', 'max')) {
|
||||
<span class="text-accent-red text-sm block">Zielwert darf höchstens 99 sein</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center gap-4 mt-8">
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleRollMode()"
|
||||
[ngClass]="{
|
||||
'bg-emerald text-white': diceForm.get('rollOver')?.value,
|
||||
'bg-deep-blue-light text-text-secondary hover:bg-deep-blue-light/80':
|
||||
!diceForm.get('rollOver')?.value,
|
||||
}"
|
||||
class="py-3 px-8 rounded-lg text-lg"
|
||||
appPlaySound
|
||||
>
|
||||
Über Zielwert
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleRollMode()"
|
||||
[ngClass]="{
|
||||
'bg-emerald text-white': !diceForm.get('rollOver')?.value,
|
||||
'bg-deep-blue-light text-text-secondary hover:bg-deep-blue-light/80':
|
||||
diceForm.get('rollOver')?.value,
|
||||
}"
|
||||
class="py-3 px-8 rounded-lg text-lg"
|
||||
appPlaySound
|
||||
>
|
||||
Unter Zielwert
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if (rolledValue() !== null) {
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center justify-center">
|
||||
@if (win()) {
|
||||
<svg
|
||||
class="w-6 h-6 text-emerald mr-2"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="text-emerald text-base font-semibold">
|
||||
Du hast gewonnen! Auszahlung: {{ payout() | currency: 'EUR' : 'symbol' : '1.2-2' }}
|
||||
</p>
|
||||
} @else {
|
||||
<svg
|
||||
class="w-6 h-6 text-accent-red mr-2"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="text-accent-red text-base font-semibold">Du hast verloren.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-1 space-y-6">
|
||||
<div class="card p-4">
|
||||
<h3 class="section-heading text-xl mb-4">Spielinformationen</h3>
|
||||
<form [formGroup]="diceForm" (ngSubmit)="roll()" class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-text-secondary">Möglicher Gewinn:</span>
|
||||
<span class="text-emerald">{{
|
||||
potentialWin() | currency: 'EUR' : 'symbol' : '1.2-2'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-text-secondary">Gewinnchance:</span>
|
||||
<span class="text-white">{{ winChance() | number: '1.0-2' }}%</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 mb-4">
|
||||
<button
|
||||
type="button"
|
||||
(click)="setBetAmount(0.1)"
|
||||
class="button-primary py-2 text-sm"
|
||||
appPlaySound
|
||||
>
|
||||
10%
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
(click)="setBetAmount(0.25)"
|
||||
class="button-primary py-2 text-sm"
|
||||
appPlaySound
|
||||
>
|
||||
25%
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
(click)="setBetAmount(0.5)"
|
||||
class="button-primary py-2 text-sm"
|
||||
appPlaySound
|
||||
>
|
||||
50%
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
(click)="setBetAmount(1)"
|
||||
class="button-primary py-2 text-sm"
|
||||
appPlaySound
|
||||
>
|
||||
100%
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between">
|
||||
<label for="betAmount" class="text-sm text-text-secondary">Einsatzbetrag</label>
|
||||
</div>
|
||||
<input
|
||||
id="betAmount"
|
||||
type="number"
|
||||
formControlName="betAmount"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
class="w-full bg-deep-blue-light text-white rounded-lg p-2 focus:outline-none focus:ring-1 focus:ring-emerald"
|
||||
class="w-full px-3 py-2 bg-deep-blue-light text-white rounded focus:outline-none focus:ring-2 ring-emerald"
|
||||
/>
|
||||
@if (hasError('betAmount', 'required')) {
|
||||
<span class="text-accent-red text-sm mt-1 block">Bet Amount is required</span>
|
||||
<span class="text-accent-red text-xs mt-1 block">Einsatz ist erforderlich</span>
|
||||
}
|
||||
@if (hasError('betAmount', 'min')) {
|
||||
<span class="text-accent-red text-sm mt-1 block"
|
||||
>Bet Amount must be at least 0.01</span
|
||||
<span class="text-accent-red text-xs mt-1 block"
|
||||
>Einsatz muss mindestens 0.01 sein</span
|
||||
>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="block text-text-secondary mb-2">Roll Mode:</div>
|
||||
<div class="roll-mode flex rounded-lg overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleRollMode()"
|
||||
[ngClass]="{
|
||||
'bg-emerald text-white': diceForm.get('rollOver')?.value,
|
||||
'bg-deep-blue-light text-text-secondary': !diceForm.get('rollOver')?.value,
|
||||
}"
|
||||
class="flex-1 py-2 text-center font-semibold transition-colors duration-200"
|
||||
>
|
||||
Roll Over
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggleRollMode()"
|
||||
[ngClass]="{
|
||||
'bg-emerald text-white': !diceForm.get('rollOver')?.value,
|
||||
'bg-deep-blue-light text-text-secondary': diceForm.get('rollOver')?.value,
|
||||
}"
|
||||
class="flex-1 py-2 text-center font-semibold transition-colors duration-200"
|
||||
>
|
||||
Roll Under
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="button-primary w-full py-3 font-bold flex items-center justify-center"
|
||||
appPlaySound
|
||||
>
|
||||
Würfeln
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="targetValue" class="block text-text-secondary mb-2"
|
||||
>Target Value: {{ diceForm.get('targetValue')?.value | number: '1.0-2' }}</label
|
||||
>
|
||||
<input
|
||||
id="targetValue"
|
||||
type="range"
|
||||
formControlName="targetValue"
|
||||
min="1"
|
||||
max="100"
|
||||
step="0.01"
|
||||
class="w-full h-2 bg-deep-blue-light rounded-lg appearance-none cursor-pointer range-lg accent-emerald"
|
||||
/>
|
||||
@if (hasError('targetValue', 'required')) {
|
||||
<span class="text-accent-red text-sm mt-1 block">Target Value is required</span>
|
||||
}
|
||||
@if (hasError('targetValue', 'min')) {
|
||||
<span class="text-accent-red text-sm mt-1 block"
|
||||
>Target Value must be at least 1</span
|
||||
>
|
||||
}
|
||||
@if (hasError('targetValue', 'max')) {
|
||||
<span class="text-accent-red text-sm mt-1 block"
|
||||
>Target Value must be at most 100</span
|
||||
>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info space-y-2 text-text-secondary">
|
||||
<p>
|
||||
Win Chance: <span class="text-white">{{ winChance() | number: '1.0-2' }}%</span>
|
||||
</p>
|
||||
<p>
|
||||
Potential Win:
|
||||
<span class="text-white">{{
|
||||
potentialWin() | currency: 'EUR' : 'symbol' : '1.2-2'
|
||||
}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button-primary w-full py-2 font-bold">Roll Dice</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-3 card p-8 flex items-center justify-center">
|
||||
@if (rolledValue() !== null) {
|
||||
<div class="text-white text-center text-8xl font-bold">
|
||||
{{ rolledValue() }}
|
||||
</div>
|
||||
}
|
||||
<div class="card p-4">
|
||||
<h4 class="text-lg font-semibold mb-2">Spielanleitung</h4>
|
||||
<ul class="text-sm text-text-secondary space-y-1">
|
||||
<li>• Setze deinen Einsatz und Zielwert</li>
|
||||
<li>• Wähle "Über Zielwert" oder "Unter Zielwert"</li>
|
||||
<li>• Gewinne, wenn der Würfel zu deinen Gunsten fällt</li>
|
||||
<li>• Höheres Risiko = höhere Belohnung</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (rolledValue() !== null) {
|
||||
<div class="result max-w-sm mx-auto card p-6 mt-8 text-center">
|
||||
@if (win()) {
|
||||
<p class="text-emerald text-base font-semibold">
|
||||
You Won! Payout: {{ payout() | currency: 'EUR' : 'symbol' : '1.2-2' }}
|
||||
</p>
|
||||
} @else {
|
||||
<p class="text-accent-red text-base font-semibold">You Lost.</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
Reference in a new issue