Compare commits

..

2 commits

Author SHA1 Message Date
lziemke
b48b7a0b1e shit
Some checks failed
CI / Get Changed Files (pull_request) Successful in 8s
Pull Request Labeler / labeler (pull_request_target) Successful in 14s
Claude PR Review / claude-code (pull_request) Successful in 20s
Label PRs based on size / Check PR size (pull_request) Failing after 30s
CI / oxlint (pull_request) Successful in 3m53s
CI / prettier (pull_request) Successful in 3m56s
CI / eslint (pull_request) Successful in 4m12s
CI / test-build (pull_request) Successful in 4m38s
Build docs / build-docs (pull_request) Successful in 5m0s
CI / Docker frontend validation (pull_request) Successful in 4m43s
CI / Backend Tests (pull_request) Successful in 5m4s
CI / Checkstyle Main (pull_request) Successful in 5m8s
CI / Docker backend validation (pull_request) Successful in 1m48s
2025-06-05 13:13:23 +02:00
474f39097d chore: Add pipeline
All checks were successful
CI / Get Changed Files (pull_request) Successful in 9s
Pull Request Labeler / labeler (pull_request_target) Successful in 4s
Label PRs based on size / Check PR size (pull_request) Successful in 23s
Claude PR Review / claude-code (pull_request) Successful in 31s
CI / oxlint (pull_request) Successful in 26s
CI / eslint (pull_request) Successful in 34s
CI / prettier (pull_request) Successful in 38s
CI / Docker frontend validation (pull_request) Successful in 23s
CI / Docker backend validation (pull_request) Successful in 17s
CI / test-build (pull_request) Successful in 1m10s
CI / Checkstyle Main (pull_request) Successful in 1m28s
CI / Backend Tests (pull_request) Successful in 3m16s
Build docs / build-docs (pull_request) Successful in 5m54s
2025-06-04 12:13:53 +02:00
14 changed files with 232 additions and 624 deletions

View file

@ -1,28 +1,34 @@
name: Build docs
on:
push:
branches: [main]
pull_request:
jobs:
build-docs:
runs-on: ubuntu-latest
container:
image: git.kjan.de/actions/runner-latex:latest
env:
# Edit here with the names of your latex file and directory (can use ".")
DIR: docs
FILE: projektdokumentation.tex
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Install TeXlive
env:
ACCEPT_EULA: y
DEBIAN_FRONTEND: noninteractive
run: |
echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true" | sudo debconf-set-selections
sudo apt-get update && sudo apt-get install texlive-lang-german texlive-xetex texlive texlive-publishers texlive-science latexmk cm-super ttf-mscorefonts-installer -y
sudo fc-cache -f -v
- name: LaTeX compile
working-directory: ${{ env.DIR }}
run: latexmk -pdf -xelatex ${{ env.FILE }}
- name: Upload artifacts
uses: https://git.kjan.de/actions/upload-artifact@v4
uses: https://git.kjan.de/actions/upload-artifact@v3
with:
name: Doku
path: docs/projektdokumentation.pdf

View file

@ -19,9 +19,6 @@
\usepackage{enumitem}
\usepackage{tikz}
\usetikzlibrary{arrows.meta, positioning}
\usepackage{pdflscape}
\usepackage{afterpage}
\usepackage{needspace}
% Seitenränder
\geometry{
@ -80,9 +77,9 @@
\centering
\vspace*{2cm}
{\Huge\bfseries Trustworthy Casino\par}
{\Huge\bfseries Casino Gaming Platform GURWEABSOHN\par}
\vspace{1.5cm}
{\Large\itshape Projektdokumentation für das Mittelstufenprojekt\par}
{\Large\itshape Projektdokumentation für die IHK-Abschlussprüfung\par}
\vspace{2cm}
{\large\bfseries Fachinformatiker für Anwendungsentwicklung\par}
@ -90,10 +87,12 @@
{\large
\begin{tabular}{ll}
\textbf{Prüfling:} & [Name des Prüflings] \\
\textbf{Prüflingsnummer:} & [Prüflingsnummer] \\
\textbf{Ausbildungsbetrieb:} & Hitec GmbH \\
\textbf{Projektbetreuer:} & Herr Heidemann / Frau Deeken \\
\textbf{Projektdauer:} & 10 Wochen \\
\textbf{Abgabedatum:} & 12.06.2025 \\
\textbf{Projektbetreuer:} & [Betreuer] \\
\textbf{Projektdauer:} & 70 Stunden \\
\textbf{Abgabedatum:} & \today \\
\end{tabular}
\par}
@ -110,16 +109,17 @@
\chapter{Beschreibung}
\section{Umfeld}
Das Projekt wurde im Rahmen des Mittelstufenprojektes entwickelt.
Das Projekt wurde im Rahmen der Abschlussprüfung zum Fachinformatiker für Anwendungsentwicklung bei der Hitec GmbH entwickelt.
\subsection{Produktportfolio der Hitec GmbH}
Die Hitec GmbH ist ein innovatives IT-Unternehmen, das sich auf die Entwicklung moderner Webanwendungen und digitaler Lösungen spezialisiert hat. Das Unternehmen deckt ein breites Spektrum an Technologien ab.
Die Hitec GmbH ist ein innovatives IT-Unternehmen, das sich auf die Entwicklung moderner Webanwendungen und digitaler Lösungen spezialisiert hat. Das Unternehmen deckt ein breites Spektrum an Technologien ab, von klassischen Enterprise-Anwendungen bis hin zu modernen Cloud-nativen Lösungen.
\subsection{Weg zur Produktidee}
Die Casino Gaming Platform entstand als LF08 Projekt mit dem Ziel, eine vollständige Online-Casino-Plattform zu entwickeln. Die Produktidee basiert auf:
\begin{itemize}
\item \textbf{Gamification-Trend:} Wachsende Nachfrage nach Online-Gaming-Plattformen
\item \textbf{Technologie-Demonstration:} Showcase moderner Full-Stack-Entwicklung (Angular 20 + Spring Boot 3.5)
\item \textbf{Bildungsziel:} Praktische Anwendung von Enterprise-Patterns und modernen Web-Technologien
\item \textbf{Marktforschung:} Integration aktueller Standards (OAuth2, Stripe, responsive Design)
\end{itemize}
@ -182,14 +182,14 @@ Detaillierte Beschreibung des Problems oder der Marktlücke, welche unser Produk
\chapter{Durchführung}
\section{Vorgehensweise}
Für die Projektdurchführung wurde Scrum als Vorgehensmodell gewählt bzw. vorgeschrieben. Dadurch wird am Anfang des Projektes und eines Sprints festgelegt, was gemacht werden soll, und jeder ist auf dem gleichen Wissensstand. Des Weiteren gibt es tägliche Updates vom Fortschritt, Probleme können schnell erkannt und angesprochen werden. Dazu kann man auch neue Anforderungen flexibel mit einbringen.
Für die Projektdurchführung wurde Scrum als Vorgehensmodell gewählt. Dadurch wird am Anfang des Projektes und eines Sprints festgelegt, was gemacht werden soll, und jeder ist auf dem gleichen Wissensstand. Des Weiteren gibt es tägliche Updates vom Fortschritt, Probleme können schnell erkannt und angesprochen werden. Dazu kann man auch neue Anforderungen flexibel mit einbringen.
\subsection{Verwendete Tools und Methoden}
\begin{itemize}
\item \textbf{Scrum:} Agile Projektmanagement-Methodik
\item \textbf{Jira:} Ticketing und Sprint-Planning
\item \textbf{Git:} Versionskontrolle mit Feature-Branch-Workflow
\item \textbf{CI/CD:} Automatisierte Build- und Deployment-Pipelines welche gleichzeitig auch die Qualitätssicherung übernimmt
\item \textbf{CI/CD:} Automatisierte Build- und Deployment-Pipelines
\end{itemize}
\section{Umsetzung}
@ -266,203 +266,33 @@ Guards → Components → Services → HTTP Interceptors → Backend APIs
Angular Router → Lazy Loading → Feature Modules
\end{verbatim}
\subsection{Systemarchitektur nach C4-Modell}
Die Casino Gaming Platform basiert auf einer modernen, schichtbasierten Architektur mit klarer Trennung zwischen Frontend und Backend. Die Darstellung erfolgt mittels des C4-Architekturmodells (Context, Container, Component, Code), welches eine hierarchische Sichtweise auf die Softwarearchitektur ermöglicht.
\subsection{Systemarchitektur}
Die Casino Gaming Platform basiert auf einer modernen Microservice-Architektur mit klarer Trennung zwischen Frontend und Backend.
\clearpage
\begin{landscape}
\subsubsection{Ebene 1: Systemkontext-Diagramm}
Das Systemkontext-Diagramm stellt die Casino Gaming Platform in ihrem geschäftlichen Umfeld dar und zeigt die Beziehungen zu externen Akteuren und Systemen. Es verdeutlicht, wer das System nutzt und mit welchen externen Systemen es interagiert.
\vspace{0.5cm}
\subsubsection{Gesamtarchitektur}
\begin{tikzpicture}[
person/.style={rectangle, draw=blue!80, fill=blue!15, minimum width=3cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
system/.style={rectangle, draw=blue!80, fill=blue!60, minimum width=5cm, minimum height=2.5cm, align=center, text=white, rounded corners=5pt, thick},
external/.style={rectangle, draw=gray!80, fill=gray!20, minimum width=3.5cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
->, >=Stealth, node distance=4cm, thick
box/.style={rectangle, draw, minimum width=3cm, minimum height=1.5cm, align=center},
->, >=Stealth, node distance=2cm and 1.5cm
]
% Akteure
\node[person] (player) at (-8, 3) {\textbf{Casino-Spieler}\\Nutzt die Plattform\\für Online-Gaming};
\node[person] (admin) at (-8, -3) {\textbf{System-Administrator}\\Verwaltet Plattform\\und Benutzer};
% Top row
\node[box] (angular) {Angular\\Frontend};
\node[box, right=of angular] (spring) {Spring Boot\\Backend};
\node[box, right=of spring] (postgres) {PostgreSQL\\Database};
% Hauptsystem
\node[system] (casino) at (0, 0) {\textbf{Casino Gaming Platform}\\Bietet Online-Casino-Spiele\\mit virtueller Währung und\\Zahlungsabwicklung};
% Bottom row
\node[box, below=of angular] (keycloak) {Keycloak\\Auth Server};
\node[box, below=of spring] (stripe) {Stripe API\\Payment};
% Externe Systeme
\node[external] (stripe) at (8, 3) {\textbf{Stripe API}\\Zahlungsabwicklung};
\node[external] (email) at (8, 0) {\textbf{E-Mail-Service}\\Verifizierung \&\\Benachrichtigungen};
\node[external] (oauth) at (8, -3) {\textbf{OAuth-Provider}\\GitHub, Google};
% Beziehungen
\draw (player) -- (casino) node[midway, above] {Spielt Spiele, tätigt Einzahlungen};
\draw (admin) -- (casino) node[midway, below] {Administriert System};
\draw (casino) -- (stripe) node[midway, above] {Verarbeitet Zahlungen};
\draw (casino) -- (email) node[midway, above] {Sendet E-Mails};
\draw (casino) -- (oauth) node[midway, below] {Authentifiziert Benutzer};
\end{tikzpicture}
\end{landscape}
\clearpage
\begin{landscape}
\subsubsection{Ebene 2: Container-Diagramm}
Das Container-Diagramm zeigt die High-Level-Architektur der Software und stellt die verschiedenen ausführbaren Einheiten (Container) sowie deren Kommunikation dar. Jeder Container repräsentiert eine separate deploybare/ausführbare Einheit wie eine Webanwendung, Datenbank oder Microservice.
\vspace{0.5cm}
\begin{tikzpicture}[
webapp/.style={rectangle, draw=blue!80, fill=blue!25, minimum width=4cm, minimum height=2.5cm, align=center, rounded corners=4pt, thick},
api/.style={rectangle, draw=green!80, fill=green!25, minimum width=4cm, minimum height=2.5cm, align=center, rounded corners=4pt, thick},
database/.style={rectangle, draw=red!80, fill=red!25, minimum width=3.5cm, minimum height=2.2cm, align=center, rounded corners=4pt, thick},
external/.style={rectangle, draw=gray!80, fill=gray!20, minimum width=3.2cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
->, >=Stealth, node distance=4cm, thick
]
% Akteur
\node[webapp] (person) at (-10, 5) {\textbf{Casino-Spieler}\\Person\\Nutzt Webbrowser\\oder Mobile App};
% Frontend-Container
\node[webapp] (frontend) at (-10, 1) {\textbf{Webanwendung}\\Angular 20, TypeScript\\Liefert statische Inhalte\\und Casino-Spiele-UI};
% Backend-Container
\node[api] (backend) at (0, 1) {\textbf{API-Anwendung}\\Spring Boot, Java\\Stellt Spiellogik,\\Benutzerverwaltung und\\Geschäftsregeln bereit};
% Datenbank
\node[database] (database) at (0, -4) {\textbf{Datenbank}\\PostgreSQL\\Speichert Benutzerkonten,\\Spielhistorie und\\Transaktionen};
% Externe Systeme
\node[external] (stripe) at (9, 4) {\textbf{Stripe API}\\Zahlungssystem};
\node[external] (email) at (9, 1) {\textbf{Mailpit}\\E-Mail-Service};
\node[external] (oauth) at (9, -2) {\textbf{OAuth-Provider}\\Authentifizierungsdienste};
% Beziehungen
\draw (person) -- (frontend) node[midway, right] {HTTPS};
\draw (frontend) -- (backend) node[midway, above] {REST API\\JSON/HTTPS};
\draw (backend) -- (database) node[midway, left] {JDBC\\SQL};
\draw (backend) -- (stripe) node[midway, above] {HTTPS\\Webhooks};
\draw (backend) -- (email) node[midway, above] {SMTP};
\draw (backend) -- (oauth) node[midway, below] {OAuth2\\HTTPS};
\end{tikzpicture}
\end{landscape}
\clearpage
\begin{landscape}
\subsubsection{Ebene 3: Komponenten-Diagramm - Backend API}
Das Komponenten-Diagramm zeigt die interne Struktur des Backend API-Containers und stellt die wichtigsten Softwarekomponenten sowie deren Abhängigkeiten dar. Es verdeutlicht die Architektur nach dem MVC-Pattern mit Controller-, Service- und Repository-Schichten.
\vspace{0.5cm}
\begin{tikzpicture}[
component/.style={rectangle, draw=yellow!80, fill=yellow!15, minimum width=3.2cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
controller/.style={rectangle, draw=blue!80, fill=blue!20, minimum width=3.2cm, minimum height=1.8cm, align=center, rounded corners=3pt, thick},
service/.style={rectangle, draw=green!80, fill=green!20, minimum width=3.2cm, minimum height=1.6cm, align=center, rounded corners=3pt, thick},
repository/.style={rectangle, draw=orange!80, fill=orange!20, minimum width=3.2cm, minimum height=1.4cm, align=center, rounded corners=3pt, thick},
->, >=Stealth, node distance=3cm, thick
]
% Controller (API-Schicht)
\node[controller] (auth-ctrl) at (-8, 5) {\textbf{Auth Controller}\\Anmeldung, Registrierung,\\OAuth};
\node[controller] (game-ctrl) at (-3, 5) {\textbf{Spiele-Controller}\\Blackjack, Slots,\\Würfel, etc.};
\node[controller] (deposit-ctrl) at (2, 5) {\textbf{Einzahlungs-Controller}\\Zahlungs-\\abwicklung};
\node[controller] (user-ctrl) at (7, 5) {\textbf{Benutzer-Controller}\\Profil-\\verwaltung};
% Services (Geschäftslogik-Schicht)
\node[service] (auth-svc) at (-8, 2.5) {\textbf{Auth Service}\\Authentifizierungs-\\logik};
\node[service] (game-svc) at (-3, 2.5) {\textbf{Spiele-Services}\\Spiellogik und\\Regeln};
\node[service] (balance-svc) at (2, 2.5) {\textbf{Guthaben-Service}\\Transaktions-\\verwaltung};
\node[service] (user-svc) at (7, 2.5) {\textbf{Benutzer-Service}\\Benutzer-\\verwaltung};
% Repositories (Datenschicht)
\node[repository] (user-repo) at (-5.5, 0) {\textbf{Benutzer-Repository}\\JPA/Hibernate};
\node[repository] (game-repo) at (-0.5, 0) {\textbf{Spiele-Repository}\\JPA/Hibernate};
\node[repository] (transaction-repo) at (4.5, 0) {\textbf{Transaktions-Repo}\\JPA/Hibernate};
% Sicherheitskomponenten
\node[component] (security) at (-13, 2.5) {\textbf{Security Config}\\JWT-Filter,\\CORS, OAuth2};
% Relationships
\draw (auth-ctrl) -- (auth-svc);
\draw (game-ctrl) -- (game-svc);
\draw (deposit-ctrl) -- (balance-svc);
\draw (user-ctrl) -- (user-svc);
\draw (auth-svc) -- (user-repo);
\draw (game-svc) -- (game-repo);
\draw (balance-svc) -- (transaction-repo);
\draw (user-svc) -- (user-repo);
\draw (security) -- (auth-svc);
\end{tikzpicture}
\end{landscape}
\clearpage
\begin{landscape}
\subsubsection{Ebene 4: Code-Diagramm - Blackjack Service Implementierung}
Das Code-Diagramm zeigt exemplarisch die Implementierungsdetails des Blackjack Game Service auf Klassenebene. Es stellt die wichtigsten Klassen, Interfaces und deren Beziehungen dar, die für die Blackjack-Spiellogik verantwortlich sind.
\vspace{0.5cm}
\begin{tikzpicture}[
class/.style={rectangle, draw=cyan!80, fill=cyan!15, minimum width=3.5cm, minimum height=3cm, align=center, rounded corners=4pt, thick},
interface/.style={rectangle, draw=yellow!80, fill=yellow!15, minimum width=3cm, minimum height=2cm, align=center, rounded corners=4pt, thick},
->, >=Stealth, node distance=4.5cm, thick
]
% Interface - mit mehr Abstand
\node[interface] (game-interface) at (0, 6) {\textbf{<<Interface>>}\\GameService\\+spielStarten()\\+zugVerarbeiten()\\+ergebnisBerechnen()};
% Haupt-Service-Klasse
\node[class] (blackjack-service) at (0, 1) {\textbf{BlackjackService}\\implementiert GameService\\- kartendeck: Deck\\- spielRepository: Repository\\+ neuesSpielStarten(einsatz)\\+ ziehen(spielId)\\+ halten(spielId)\\+ verdoppeln(spielId)};
% Unterstützende Klassen - mit größerem Abstand
\node[class] (card-deck) at (-8, 1) {\textbf{Kartendeck}\\- karten: List<Karte>\\+ mischen()\\+ karteGeben()\\+ zurücksetzen()};
\node[class] (game-entity) at (8, 1) {\textbf{BlackjackSpielEntity}\\- id: Long\\- spielerKarten: List<Karte>\\- dealerKarten: List<Karte>\\- einsatzBetrag: BigDecimal\\- spielZustand: SpielZustand};
\node[class] (balance-service) at (0, -3.5) {\textbf{GuthabenService}\\+ guthabenAktualisieren()\\+ ausreichendGuthaben()\\+ transaktionErstellen()};
% Beziehungen
\draw (blackjack-service) -- (game-interface) node[midway, right] {implementiert};
\draw (blackjack-service) -- (card-deck) node[midway, above] {verwendet};
\draw (blackjack-service) -- (game-entity) node[midway, above] {verwaltet};
\draw (blackjack-service) -- (balance-service) node[midway, right] {abhängig von};
\end{tikzpicture}
\end{landscape}
\clearpage
\begin{landscape}
\subsubsection{Deployment-Diagramm}
Das Deployment-Diagramm zeigt die Verteilung der Software-Container auf die physische/virtuelle Infrastruktur und stellt die Laufzeitumgebung der Anwendung dar. Es verdeutlicht, wie die verschiedenen Komponenten in der Produktionsumgebung deployed werden.
\vspace{0.5cm}
\begin{tikzpicture}[
node/.style={rectangle, draw=gray!80, fill=gray!15, minimum width=5cm, minimum height=4cm, align=center, rounded corners=5pt, thick},
container/.style={rectangle, draw=blue!80, fill=blue!20, minimum width=3cm, minimum height=2cm, align=center, rounded corners=4pt, thick},
->, >=Stealth, node distance=6cm, thick
]
% Infrastruktur-Knoten
\node[node] (docker-host) at (0, 0) {\textbf{Docker-Host}\\Ubuntu Linux\\Docker Engine 24.x};
% Container innerhalb des Docker-Hosts - mit mehr Abstand
\node[container] (nginx) at (-7, 3.5) {\textbf{nginx}\\Webserver\\Port 80/443};
\node[container] (angular) at (-2, 3.5) {\textbf{Frontend}\\Angular-App\\Statische Dateien};
\node[container] (spring) at (2, 3.5) {\textbf{Backend}\\Spring Boot\\Port 8080};
\node[container] (postgres) at (7, 3.5) {\textbf{Datenbank}\\PostgreSQL\\Port 5432};
\node[container] (mailpit) at (2, -3.5) {\textbf{Mailpit}\\E-Mail-Service\\Port 1025/8025};
% Externe Dienste - mit mehr Abstand
\node[node] (cloud) at (9, 0) {\textbf{Cloud-Dienste}\\Stripe API\\OAuth-Provider};
% Beziehungen
\draw (nginx) -- (angular);
% Arrows top row
\draw (angular) -- (spring);
\draw (spring) -- (postgres);
\draw (spring) -- (mailpit);
\draw (spring) -- (cloud);
% Arrows to bottom row
\draw (angular.south) -- (keycloak.north);
\draw (spring.south) -- (stripe.north);
\end{tikzpicture}
\end{landscape}
\subsubsection{Backend-Paketstruktur}
Das Backend folgt dem Domain-Driven Design (DDD) Ansatz:
@ -505,73 +335,8 @@ Das Backend folgt dem Domain-Driven Design (DDD) Ansatz:
.3 UserService.java.
}
\clearpage
\begin{landscape}
\subsubsection{Ebene 3: Komponenten-Diagramm - Frontend-Anwendung}
Das Komponenten-Diagramm zeigt die interne Struktur der Angular Frontend-Anwendung und stellt die modulare Architektur mit Feature-Modulen, Services und Guards dar. Es verdeutlicht die Anwendung des Angular-Framework-Patterns.
\vspace{0.5cm}
\begin{tikzpicture}[
module/.style={rectangle, draw=purple!80, fill=purple!20, minimum width=3.5cm, minimum height=2.2cm, align=center, rounded corners=4pt, thick},
component/.style={rectangle, draw=blue!80, fill=blue!20, minimum width=3cm, minimum height=1.6cm, align=center, rounded corners=3pt, thick},
service/.style={rectangle, draw=green!80, fill=green!20, minimum width=3cm, minimum height=1.6cm, align=center, rounded corners=3pt, thick},
guard/.style={rectangle, draw=orange!80, fill=orange!20, minimum width=3cm, minimum height=1.6cm, align=center, rounded corners=3pt, thick},
->, >=Stealth, node distance=3.5cm, thick
]
% Kern-Module
\node[module] (app) at (4, 6) {\textbf{App}\\Haupt-Modul};
\node[module] (shared) at (-6, 2) {\textbf{Shared}\\Gemeinsame Komponenten};
% Feature-Module
\node[module] (auth) at (-1, 2) {\textbf{Auth}\\Anmeldung, OAuth2};
\node[module] (games) at (3.75, 2) {\textbf{Spiele}\\Blackjack, Slots, Würfel};
\node[module] (deposit) at (8.25, 2) {\textbf{Einzahlung}\\Stripe};
\node[module] (home) at (12.25, 2){\textbf{Home}\\Dashboard};
% Services
\node[service] (auth-svc) at (-1, -1.5) {\textbf{Auth}\\JWT, Benutzerstatus};
\node[service] (user-svc) at (12.25, -1.5) {\textbf{User}\\Profil, Guthaben};
\node[service] (game-svc) at (3.75, -1.5) {\textbf{Spiele}\\API, Logik};
\node[service] (payment-svc) at (8.25, -1.5) {\textbf{Payment}\\Stripe};
% Guards
\node[guard] (auth-guard) at (-1.5, -4.5) {\textbf{Auth}\\Routen-Schutz};
\node[guard] (http-interceptor) at (2, -4.5) {\textbf{HTTP}\\JWT, CORS};
% Relationships
\draw (app) -- (shared);
\draw (app) -- (auth);
\draw (app) -- (games);
\draw (app) -- (deposit);
\draw (app) -- (home);
\draw (auth) -- (auth-svc);
\draw (games) -- (game-svc);
\draw (deposit) -- (payment-svc);
\draw (home) -- (user-svc);
\draw (auth-guard) -- (auth-svc);
\draw (http-interceptor) -- (auth-svc);
\end{tikzpicture}
\end{landscape}
\subsubsection{Architektur-Zusammenfassung}
Die Casino Gaming Platform implementiert eine moderne, mehrschichtige Architektur, die folgende Prinzipien befolgt:
\begin{itemize}
\item \textbf{Separation of Concerns:} Klare Trennung zwischen Präsentations-, Geschäftslogik- und Datenschicht
\item \textbf{Modularität:} Feature-basierte Modularisierung sowohl im Frontend als auch Backend
\item \textbf{Skalierbarkeit:} Container-basierte Architektur ermöglicht horizontale Skalierung
\item \textbf{Sicherheit:} Umfassende Sicherheitskonzepte mit JWT, OAuth2 und CORS-Schutz
\item \textbf{Wartbarkeit:} Standardisierte Patterns (MVC, Repository, Service Layer) erleichtern die Wartung
\end{itemize}
Die C4-Darstellung zeigt die Architektur auf verschiedenen Abstraktionsebenen und ermöglicht es unterschiedlichen Stakeholdern, die für sie relevanten Aspekte zu verstehen - von der Geschäftslogik bis hin zu technischen Implementierungsdetails.
\subsubsection{Frontend-Paketstruktur (Detailansicht)}
Die detaillierte Paketstruktur des Frontend zeigt die modulare Organisation:
\subsubsection{Frontend-Modulstruktur}
Das Frontend ist modular aufgebaut mit Feature-Modulen:
\dirtree{%
.1 app/feature/.

View file

@ -21,8 +21,7 @@
{
"glob": "**/*",
"input": "public"
},
"src/assets"
}
],
"styles": [
"src/styles.css"

View file

@ -61,9 +61,6 @@ export class AnimatedNumberComponent implements OnChanges, AfterViewInit {
this.countUp = new CountUp(this.numberElement.nativeElement, this.value, {
startVal: this.previousValue,
duration: this.duration,
decimalPlaces: 2,
useEasing: true,
useGrouping: false,
easingFn: (t, b, c, d) => {
if (this.ease === 'power1.out') {
return c * (1 - Math.pow(1 - t / d, 1)) + b;

View file

@ -4,7 +4,7 @@
</div>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<div class="lg:col-span-4">
<div class="lg:col-span-3">
<div class="flex justify-between items-center mb-6">
<h3 class="section-heading text-2xl">Alle Spiele</h3>
<div class="flex space-x-2">
@ -18,10 +18,8 @@
</div>
<div class="slider-container">
<div class="min-w-full space-y-4">
<!-- Top row with 3 games -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="card group" *ngFor="let game of featuredGames.slice(0, 3)">
<div class="slider-grid">
<div class="card group" *ngFor="let game of featuredGames">
<div class="relative overflow-hidden rounded-lg">
<img
[src]="game.image"
@ -43,32 +41,52 @@
</div>
</div>
</div>
</div>
</div>
<!-- Bottom row with 2 games centered -->
<div
class="grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-2xl mx-auto xl:max-w-3xl xl:gap-6"
<div class="lg:col-span-1 space-y-6">
<div class="card p-4">
<h3 class="section-heading text-xl mb-4">Konto</h3>
<div class="space-y-4">
<button class="button-primary w-full py-2" (click)="openDepositModal()">Einzahlen</button>
<app-deposit
[isOpen]="isDepositModalOpen"
(closeModalEmitter)="closeDepositModal()"
></app-deposit>
<button
class="bg-deep-blue-light hover:bg-deep-blue-contrast w-full py-2 rounded"
(click)="openTransactionModal()"
>
<div class="card group" *ngFor="let game of featuredGames.slice(3, 5)">
<div class="relative overflow-hidden rounded-lg">
<img
[src]="game.image"
[alt]="game.name"
class="w-full aspect-[4/3] object-cover transition-transform duration-300 group-hover:scale-105"
/>
<div
class="absolute inset-0 bg-gradient-to-t from-deep-blue/95 via-deep-blue/50 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-300 ease-in-out"
>
<div
class="absolute bottom-4 left-4 right-4 transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300"
>
<h4 class="game-heading">{{ game.name }}</h4>
<button class="button-primary w-full py-2" (click)="navigateToGame(game.route)">
Jetzt Spielen
Transaktionen
</button>
<app-transaction-history
[isOpen]="isTransactionModalOpen"
(closeEventEmitter)="closeTransactionModal()"
/>
</div>
</div>
<app-confirmation
[successful]="isDepositSuccessful"
(closeConfirmation)="closeDepositConfirmationModal()"
></app-confirmation>
<div class="card p-4">
<h3 class="section-heading text-xl mb-4">Letzte Transaktionen</h3>
<div class="space-y-3">
<div
class="flex justify-between items-center"
*ngFor="let transaction of (recentTransactionData | async)?.transactions"
>
<div>
<p class="text-sm font-medium">{{ transaction.status }}</p>
<p class="text-xs text-text-secondary">
{{ transaction.createdAt | date: 'd.m.y H:m' }}
</p>
</div>
</div>
<span [class]="transaction.amount > 0 ? 'text-emerald' : 'text-accent-red'">
{{ transaction.amount | currency: 'EUR' }}
</span>
</div>
</div>
</div>

View file

@ -1,18 +1,34 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { NgFor } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core';
import { AsyncPipe, CurrencyPipe, DatePipe, NgFor } from '@angular/common';
import { DepositComponent } from '../deposit/deposit.component';
import { ActivatedRoute, Router } from '@angular/router';
import { ConfirmationComponent } from '@shared/components/confirmation/confirmation.component';
import { Game } from 'app/model/Game';
import { Observable } from 'rxjs';
import { TransactionService } from '@service/transaction.service';
import format from 'ajv/dist/vocabularies/format';
import { TransactionHistoryComponent } from '../transaction-history/transaction-history.component';
import { TransactionData } from '../../model/TransactionData';
@Component({
selector: 'app-homepage',
standalone: true,
imports: [NgFor],
imports: [
CurrencyPipe,
NgFor,
DepositComponent,
ConfirmationComponent,
AsyncPipe,
DatePipe,
TransactionHistoryComponent,
],
templateUrl: './home.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class HomeComponent implements OnInit {
isDepositModalOpen = false;
isDepositSuccessful = false;
isTransactionModalOpen = false;
constructor(
public route: ActivatedRoute,
@ -62,10 +78,35 @@ export default class HomeComponent implements OnInit {
},
];
allGames: Game[] = [...this.featuredGames];
recentTransactionData: Observable<TransactionData> =
inject(TransactionService).getUsersTransactions(5);
openDepositModal() {
this.isDepositModalOpen = true;
}
closeDepositModal() {
this.isDepositModalOpen = false;
}
openDepositConfirmationModal() {
this.isDepositSuccessful = true;
}
openTransactionModal() {
this.isTransactionModalOpen = true;
}
closeDepositConfirmationModal() {
this.isDepositSuccessful = false;
}
closeTransactionModal() {
this.isTransactionModalOpen = false;
}
navigateToGame(route: string) {
this.router.navigate([route]);
}

View file

@ -21,7 +21,13 @@
(click)="showRegisterForm()"
class="w-full sm:w-auto button-primary px-6 sm:px-8 py-3 shadow-lg"
>
Jetzt registrieren
Konto erstellen
</button>
<button
(click)="showLoginForm()"
class="w-full sm:w-auto bg-slate-700 text-white hover:bg-slate-600 px-6 sm:px-8 py-3 shadow-lg rounded"
>
Anmelden
</button>
}
</div>
@ -40,63 +46,33 @@
<div class="game-card-content">
<h3 class="game-heading-sm">Slots</h3>
<p class="game-text">Klassische Spielautomaten</p>
@if (isLoggedIn()) {
<a
routerLink="game/slots"
class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
>
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div>
</div>
<div class="hidden lg:block card">
<div class="game-card-content">
<h3 class="game-heading-sm">Blackjack</h3>
<p class="game-text">Klassisches Kartenspiel</p>
@if (isLoggedIn()) {
<a
routerLink="game/blackjack"
class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
>
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div>
</div>
<div class="hidden lg:block card">
<div class="game-card-content">
<h3 class="game-heading-sm">Coinflip</h3>
<p class="game-text">Münzwurf</p>
@if (isLoggedIn()) {
<a
routerLink="game/coinflip"
routerLink="game/blackjack"
class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
>
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div>
</div>
</div>
@ -106,42 +82,22 @@
<div class="game-card-content">
<h3 class="game-heading-sm">Dice</h3>
<p class="game-text">Würfelspiel</p>
@if (isLoggedIn()) {
<a
routerLink="game/dice"
routerLink="/game/dice"
class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
>
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div>
</div>
<div class="hidden lg:block card">
<div class="game-card-content">
<h3 class="game-heading-sm">Lootboxen</h3>
<p class="game-text">Überraschungskisten</p>
@if (isLoggedIn()) {
<a
routerLink="game/lootboxes"
class="button-primary w-full py-2 inline-block text-center"
>Jetzt Spielen</a
>
Jetzt Spielen
</a>
} @else {
<button
(click)="showLoginForm()"
class="button-primary w-full py-2 inline-block text-center"
>
Jetzt Spielen
</button>
}
</div>
</div>
</div>

View file

@ -23,14 +23,15 @@ import RecoverPasswordComponent from '../auth/recover-password/recover-password.
})
export class LandingComponent implements OnInit, OnDestroy {
currentSlide = 0;
private autoplayInterval: ReturnType<typeof setInterval> | undefined;
authService: AuthService = inject(AuthService);
route: ActivatedRoute = inject(ActivatedRoute);
showLogin = signal(false);
showRegister = signal(false);
showRecoverPassword = signal(false);
isLoggedIn = signal(this.authService.isLoggedIn());
ngOnInit() {
this.startAutoplay();
document.body.style.overflow = 'auto';
if (this.route.snapshot.queryParamMap.get('login') === 'true') {
this.showLoginForm();
@ -38,6 +39,7 @@ export class LandingComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
this.stopAutoplay();
document.body.style.overflow = 'auto';
}
@ -71,13 +73,33 @@ export class LandingComponent implements OnInit, OnDestroy {
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();
}
}

View file

@ -1,120 +1,40 @@
<nav class="bg-deep-blue-light border-b border-emerald-500/30 shadow-lg">
<div class="max-w-full mx-auto px-6">
<div class="flex justify-between items-center h-16">
<div class="flex items-center space-x-2">
<a routerLink="/" class="flex items-center space-x-3 group">
<div class="flex flex-col">
<span class="text-xl font-bold text-white"> Trustworthy Casino </span>
<span class="text-xs text-emerald-400 font-medium">Trust. Play. Win.</span>
</div>
<nav class="bg-deep-blue border-b border-deep-blue-contrast">
<div class="max-w-full mx-auto px-4">
<div class="flex justify-between items-center h-14">
<div class="flex items-center space-x-6">
<a routerLink="/" class="nav-brand">
<span>Trustworthy Casino</span>
</a>
<div class="hidden md:flex items-center space-x-1">
@if (isLoggedIn()) {
<a
routerLink="/home"
class="flex items-center px-4 py-2 text-white/90 hover:text-white font-medium rounded-lg hover:bg-white/10 transition-colors duration-200"
>
<img class="mr-2 w-4 h-4" src="assets/games.svg" alt="gamess" />
Spiele
</a>
} @else {
<button
(click)="showLogin.emit()"
class="flex items-center px-4 py-2 text-white/90 hover:text-white font-medium rounded-lg hover:bg-white/10 transition-colors duration-200"
>
<img class="mr-2 w-4 h-4" src="assets/games.svg" alt="gamess" />
Spiele
</button>
}
<a routerLink="/home" class="nav-link">Spiele</a>
</div>
</div>
<div class="hidden md:flex items-center space-x-1">
<div class="hidden md:flex items-center space-x-4">
@if (!isLoggedIn()) {
<button
(click)="showLogin.emit()"
class="flex items-center px-4 py-2 text-white font-medium border border-emerald-500 rounded-lg hover:bg-emerald-500/10 transition-colors duration-200"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"
/>
</svg>
Anmelden
</button>
<button (click)="showLogin.emit()" class="button-primary px-4 py-1.5">Anmelden</button>
<button
(click)="showRegister.emit()"
class="flex items-center px-4 py-2 bg-emerald-600 text-white font-medium rounded-lg hover:bg-emerald-500 transition-colors duration-200"
class="bg-emerald-700 text-white hover:bg-emerald-600 px-4 py-1.5 rounded"
>
Jetzt registrieren
Registrieren
</button>
}
@if (isLoggedIn()) {
<div
class="flex items-center px-4 py-2 mr-2 bg-slate-700 border border-emerald-500/30 rounded-lg font-medium"
>
<span class="text-emerald-400 text-sm mr-2">Guthaben:</span>
<span
[class]="balance() < 0 ? 'text-red-400 font-bold' : 'text-white font-bold'"
class="text-sm"
class="text-white font-bold bg-deep-blue-contrast rounded-full px-4 py-2 text-sm hover:bg-deep-blue-contrast/80 hover:cursor-pointer hover:scale-105 transition-all active:scale-95 select-none duration-300"
routerLink="/home"
>
<span [class]="balance() < 0 ? 'text-accent-red' : ''">
<app-animated-number [value]="balance()" [duration]="0.5"></app-animated-number>
</span>
</div>
<button
class="flex items-center px-4 py-2 bg-emerald-600 text-white font-medium rounded-lg hover:bg-emerald-500 transition-colors duration-200"
(click)="openDepositModal()"
>
<img class="mr-2 w-3 h-3" src="assets/deposit.svg" alt="deposits" />
Einzahlen
</button>
<app-deposit
[isOpen]="isDepositModalOpen"
(closeModalEmitter)="closeDepositModal()"
></app-deposit>
<button
class="flex items-center px-4 py-2 bg-slate-700 text-white font-medium rounded-lg hover:bg-slate-600 border border-slate-600 transition-colors duration-200"
(click)="openTransactionModal()"
>
<img class="mr-2 w-4 h-4" src="assets/transaction.svg" alt="transactions" />
Transaktionen
</button>
<app-transaction-history
[isOpen]="isTransactionModalOpen"
(closeEventEmitter)="closeTransactionModal()"
/>
<button
(click)="logout()"
class="flex items-center px-4 py-2 text-red-400 font-medium border border-red-500/50 rounded-lg hover:bg-red-500/10 transition-colors duration-200"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
Abmelden
</button>
<button (click)="logout()" class="button-primary px-4 py-1.5">Abmelden</button>
}
</div>
<div class="md:hidden">
<button
(click)="toggleMenu()"
class="p-2 text-white hover:text-emerald-400 rounded-lg transition-colors duration-200"
>
<button (click)="toggleMenu()" class="nav-toggle">
<svg
class="h-6 w-6"
[class.hidden]="isMenuOpen"
@ -148,95 +68,25 @@
</div>
<div [class]="isMenuOpen ? 'block' : 'hidden'" class="md:hidden">
<div class="px-2 pt-2 pb-4 space-y-3 bg-slate-700 rounded-lg mt-2">
<a
routerLink="/home"
class="flex items-center px-4 py-3 text-white/90 hover:text-white hover:bg-white/10 rounded-lg transition-colors duration-200"
>
<img class="mr-2 w-4 h-4" src="assets/games.svg" alt="gamess" />
Spiele
</a>
<div class="border-t border-slate-600 pt-3 space-y-3">
<div class="nav-mobile-menu">
<a routerLink="/games" class="nav-mobile-link">Spiele</a>
<div class="pt-2 space-y-2">
@if (!isLoggedIn()) {
<button
(click)="showLogin.emit()"
class="w-full flex items-center justify-center px-4 py-3 text-white font-medium border border-emerald-500 rounded-lg hover:bg-emerald-500/10 transition-colors duration-200"
class="button-primary w-full py-1.5 block text-center"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"
/>
</svg>
Anmelden
</button>
<button
(click)="showRegister.emit()"
class="w-full flex items-center justify-center px-4 py-3 bg-emerald-600 text-white font-medium rounded-lg hover:bg-emerald-500 transition-colors duration-200"
class="bg-emerald-700 text-white hover:bg-emerald-600 w-full py-1.5 rounded block text-center"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"
/>
</svg>
Registrieren
</button>
}
@if (isLoggedIn()) {
<div
class="flex items-center justify-center px-4 py-3 bg-slate-700 border border-emerald-500/30 rounded-lg"
>
<span class="text-emerald-400 text-sm font-medium mr-2">Guthaben:</span>
<span
[class]="balance() < 0 ? 'text-red-400 font-bold' : 'text-white font-bold'"
class="text-sm"
>
<app-animated-number [value]="balance()" [duration]="0.5"></app-animated-number>
</span>
</div>
<button
(click)="openDepositModal()"
class="w-full flex items-center justify-center px-4 py-3 bg-emerald-600 text-white font-medium rounded-lg hover:bg-emerald-500 transition-colors duration-200"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
Einzahlen
</button>
<button
(click)="openTransactionModal()"
class="w-full flex items-center justify-center px-4 py-3 bg-slate-700 text-white font-medium rounded-lg hover:bg-slate-600 border border-slate-600 transition-colors duration-200"
>
Transaktionen
</button>
<button
(click)="logout()"
class="w-full flex items-center justify-center px-4 py-3 text-red-400 font-medium border border-red-500/50 rounded-lg hover:bg-red-500/10 transition-colors duration-200"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
Abmelden
</button>
<button (click)="logout()" class="button-primary w-full py-1.5">Abmelden</button>
}
</div>
</div>

View file

@ -12,20 +12,16 @@ import { RouterModule } from '@angular/router';
import { AuthService } from '@service/auth.service';
import { Subscription } from 'rxjs';
import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component';
import { DepositComponent } from '../../../feature/deposit/deposit.component';
import { TransactionHistoryComponent } from '../../../feature/transaction-history/transaction-history.component';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
standalone: true,
imports: [RouterModule, AnimatedNumberComponent, DepositComponent, TransactionHistoryComponent],
imports: [RouterModule, AnimatedNumberComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NavbarComponent implements OnInit, OnDestroy {
isMenuOpen = false;
isDepositModalOpen = false;
isTransactionModalOpen = false;
private authService: AuthService = inject(AuthService);
isLoggedIn = signal(this.authService.isLoggedIn());
@ -55,20 +51,4 @@ export class NavbarComponent implements OnInit, OnDestroy {
toggleMenu() {
this.isMenuOpen = !this.isMenuOpen;
}
openDepositModal() {
this.isDepositModalOpen = true;
}
closeDepositModal() {
this.isDepositModalOpen = false;
}
openTransactionModal() {
this.isTransactionModalOpen = true;
}
closeTransactionModal() {
this.isTransactionModalOpen = false;
}
}

View file

@ -1,4 +0,0 @@
<svg style="color: white;" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16">
<path fill="currentColor" d="m8 16l-2-3h1v-2h2v2h1zm7-15v8H1V1zm1-1H0v10h16z"/>
<path fill="currentColor" d="M8 2a3 3 0 1 1 0 6h5V7h1V3h-1V2zM5 5a3 3 0 0 1 3-3H3v1H2v4h1v1h5a3 3 0 0 1-3-3"/>
</svg>

Before

Width:  |  Height:  |  Size: 314 B

View file

@ -1,6 +0,0 @@
<svg style="color: white;" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512">
<path fill="currentColor"
d="M495.24 267.592L445.066 41.083A32.04 32.04 0 0 0 406.9 16.76L180.393 66.934a32 32 0 0 0-24.322 38.166l21.021 94.9H48a32.036 32.036 0 0 0-32 32v232a32.036 32.036 0 0 0 32 32h232a32.036 32.036 0 0 0 32-32V340.957l158.917-35.2a32.04 32.04 0 0 0 24.323-38.165M280 464H48V232h136.181l22.063 99.606a32.03 32.03 0 0 0 31.18 25.092a32.3 32.3 0 0 0 6.984-.769l35.6-7.886L280.02 464Zm184-189.487l-226.513 50.173l-50.173-226.51L413.824 48l50.193 226.505Z"/>
<path fill="currentColor"
d="M80 264h40v40H80zm0 128h40v40H80zm128 0h40v40h-40zm-64-64h40v40h-40zm81.456-205.433l39.054-8.644l8.644 39.055l-39.054 8.644zm152.672 97.223l39.054-8.65l8.65 39.054l-39.054 8.65zm-76.324-48.649l39.053-8.65l8.65 39.053l-39.052 8.65z"/>
</svg>

Before

Width:  |  Height:  |  Size: 882 B

View file

@ -1,12 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
style="color: white;"
>
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" color="currentColor">
<path d="M4.58 8.607L2 8.454C3.849 3.704 9.158 1 14.333 2.344c5.513 1.433 8.788 6.918 7.314 12.25c-1.219 4.411-5.304 7.337-9.8 7.406"/>
<path d="M12 22C6.5 22 2 17 2 11m11.604-1.278c-.352-.37-1.213-1.237-2.575-.62c-1.361.615-1.577 2.596.482 2.807c.93.095 1.537-.11 2.093.47c.556.582.659 2.198-.761 2.634s-2.341-.284-2.588-.509m1.653-6.484v.79m0 6.337v.873"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 639 B

View file

@ -71,10 +71,6 @@ a {
@apply font-bold text-text-primary text-sm mb-2;
}
.game-heading {
@apply font-bold text-text-primary text-lg mb-2;
}
.game-heading-xl {
@apply font-bold text-text-primary text-xl mb-2;
}