feat(ui): enhance UI elements and improve layout for game creation and rules pages
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background-image: url('/assets/images/background.png');
|
||||
--color: white;
|
||||
--color: #f8f9fa; /* Light text on dark bg */
|
||||
|
||||
/* Bootstrap variable overrides for dark mode */
|
||||
--bs-body-color: var(--color);
|
||||
--bs-link-color: #66b2ff;
|
||||
--bs-link-hover-color: #99ccff;
|
||||
--bs-border-color: rgba(255, 255, 255, 0.2);
|
||||
--bs-heading-color: var(--color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
@import "dark-mode.less";
|
||||
@import "login.less";
|
||||
|
||||
/* Provide default (light) variables so the site works even if light-mode.less fails */
|
||||
:root {
|
||||
--background-image: url('/assets/images/img.png');
|
||||
--color: #212529; /* Bootstrap body text default */
|
||||
|
||||
/* Bootstrap variable overrides for light mode */
|
||||
--bs-body-color: var(--color) !important;
|
||||
--bs-link-color: #0d6efd !important;
|
||||
--bs-link-hover-color: #0a58ca !important;
|
||||
--bs-border-color: rgba(0, 0, 0, 0.125) !important;
|
||||
--bs-heading-color: var(--color) !important;
|
||||
}
|
||||
|
||||
@background-image: var(--background-image);
|
||||
@color: var(--color);
|
||||
@keyframes slideIn {
|
||||
@@ -10,8 +23,33 @@
|
||||
}
|
||||
.game-field-background {
|
||||
background-image: @background-image;
|
||||
background-size: 100vw 100vh;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
.navbar-header{
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.navbar-toggle {
|
||||
float: none;
|
||||
margin-right:0;
|
||||
}
|
||||
|
||||
/* Ensure body text color follows theme variable and works with Bootstrap */
|
||||
body {
|
||||
color: @color;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: @color;
|
||||
padding: 0.5rem 0;
|
||||
flex-grow: 1; /* fill remaining vertical space as visual footer background */
|
||||
}
|
||||
|
||||
.game-field {
|
||||
@@ -19,6 +57,7 @@
|
||||
inset: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#sessions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -61,9 +61,7 @@ class MainMenuController @Inject()(
|
||||
}
|
||||
}
|
||||
|
||||
def rules(): Action[AnyContent] = {
|
||||
Action { implicit request =>
|
||||
Ok(views.html.mainmenu.rules())
|
||||
}
|
||||
def rules(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||
Ok(views.html.mainmenu.rules(Some(request.user)))
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,12 @@ class StubUserManager @Inject()(val config: Config) extends UserManager {
|
||||
id = java.util.UUID.fromString("223e4567-e89b-12d3-a456-426614174000"),
|
||||
name = "Leon",
|
||||
passwordHash = UserHash.hashPW("password123")
|
||||
),
|
||||
"Jakob" -> User(
|
||||
internalId = 2L,
|
||||
id = java.util.UUID.fromString("323e4567-e89b-12d3-a456-426614174000"),
|
||||
name = "Jakob",
|
||||
passwordHash = UserHash.hashPW("password123")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
@()
|
||||
|
||||
@main("Login") {
|
||||
|
||||
<div class="login-box">
|
||||
<div class="card login-card p-4">
|
||||
<div class="card-body">
|
||||
<h3 class="text-center mb-4">Login</h3>
|
||||
<h3 class="text-center mb-4 text-body">Login</h3>
|
||||
|
||||
<form action="@routes.UserController.login_Post()" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" placeholder="Enter Username" required>
|
||||
<label for="username" class="form-label text-body">Username</label>
|
||||
<input type="text" class="form-control text-body" id="username" name="username" placeholder="Enter Username" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" placeholder="Enter password" required>
|
||||
<label for="password" class="form-label text-body">Password</label>
|
||||
<input type="password" class="form-control text-body" id="password" name="password" placeholder="Enter password" required>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
@@ -34,6 +35,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="@routes.Assets.versioned("/javascripts/particles.js")"></script>
|
||||
<script>
|
||||
particlesJS.load('particles-js', 'assets/conf/particlesjs-config.json', function() {
|
||||
console.log('callback - particles.js config loaded');
|
||||
});
|
||||
</script>
|
||||
<div id="particles-js" style="background-color: rgb(182, 25, 36);
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
@@ -3,24 +3,30 @@
|
||||
* handles the rendering of the page header and body tags. It takes
|
||||
* two arguments, a `String` for the title of the page and an `Html`
|
||||
* object to insert into the body of the page.
|
||||
*@
|
||||
*@
|
||||
@(title: String)(content: Html)
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@* Here's where we render the page title `String`. *@
|
||||
<title>@title</title>
|
||||
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
|
||||
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="d-flex flex-column min-vh-100 game-field-background">
|
||||
<main>
|
||||
@* And here's where we render the `Html` object containing
|
||||
* the page content. *@
|
||||
@content
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
</footer>
|
||||
|
||||
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
@main("Create Game") {
|
||||
@navbar(user)
|
||||
<form action="@routes.MainMenuController.createGame()" method="post">
|
||||
<form action="@routes.MainMenuController.createGame()" method="post" class="game-field-background">
|
||||
<div class="w-50 mx-auto">
|
||||
<div class="mt-3">
|
||||
<label for="lobbyname" class="form-label">Lobby-Name</label>
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<label for="playeramount" class="form-label">Playeramount:</label>
|
||||
<input type="range" class="form-range" min="2" max="7" value="2" id="playeramount" name="playeramount">
|
||||
<input type="range" class="form-range text-body" min="2" max="7" value="2" id="playeramount" name="playeramount">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>2</span>
|
||||
<span>3</span>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
@(user: Option[model.users.User])
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<div class="container d-flex justify-content-center">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navBar" aria-controls="navBar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navBar">
|
||||
<a class="navbar-brand" href="@routes.MainMenuController.mainMenu()">KnockOutWhist</a>
|
||||
<div class="collapse navbar-collapse justify-content-center" id="navBar">
|
||||
<a class="navbar-brand ms-auto" href="@routes.MainMenuController.mainMenu()">KnockOutWhist</a>
|
||||
<div class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<ul class="navbar-nav mb-2 mb-lg-0">
|
||||
@if(user.isDefined) {
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="#">Create Game</a>
|
||||
<a class="nav-link active" aria-current="page" href="@routes.MainMenuController.mainMenu()">Create Game</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disabled" aria-disabled="true">Lobbies</a>
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
@()
|
||||
@(user: Option[model.users.User])
|
||||
|
||||
@main("Rules") {
|
||||
<div id="rules" class="game-field game-field-background">
|
||||
<table>
|
||||
<caption>Rules Overview and Equipment</caption>
|
||||
<thead>
|
||||
@navbar(user)
|
||||
<div id="rules">
|
||||
<div class="container my-4">
|
||||
<div class="card shadow-sm rounded-3">
|
||||
<div class="card-header text-white text-center">
|
||||
<h4 class="mb-0 text-body">Game Rules Overview</h4>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover mb-0 align-middle">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Section</th>
|
||||
<th>Details</th>
|
||||
<th scope="col">Section</th>
|
||||
<th scope="col">Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -17,7 +24,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aim</td>
|
||||
<td>To be the last player left in at the end of the game, with the object in each hand being to win a majority of tricks.</td>
|
||||
<td>To be the last player left at the end of the game, with the object in each hand being to win a majority of tricks.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Equipment</td>
|
||||
@@ -33,7 +40,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Deal (Subsequent Hands)</td>
|
||||
<td>The deal rotates clockwise. The player who took the most tricks in the previous hand selects the trump suit. If there's a tie for the highest number of tricks, players cut cards to decide who calls trumps. One fewer card is dealt in each successive hand until the final hand consists of one card each.</td>
|
||||
<td>The deal rotates clockwise. The player who took the most tricks in the previous hand selects the trump suit. If there's a tie, players cut cards to decide who calls trumps. One fewer card is dealt in each successive hand until the final hand consists of one card each.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Play</td>
|
||||
@@ -55,9 +62,15 @@
|
||||
<td>Winning the Game</td>
|
||||
<td>The game is won when a player takes all the tricks in a round, as all other players are knocked out, leaving only one player remaining.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td>Dog Life</td>
|
||||
<td>The first player who takes no tricks is awarded a "dog's life". In the next hand, that player is dealt one card and can decide which trick to play it to. Each time a trick is played the "dog" may either play the card or knock on the table and wait to play it later. If the dog wins a trick, the player to the left leads to the next and the dog re-enters the game properly in the next hand. If the dog fails, they are knocked out.</td>
|
||||
<td>The first player who takes no tricks is awarded a "dog's life". In the next hand, that player is dealt one card and can decide which trick to play it to. Each time a trick is played the "dog" may either play the card or knock on the table and wait to play it later. If the dog wins a trick, the player to the left leads the next and the dog re-enters the game properly in the next hand. If the dog fails, they are knocked out.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -1,3 +1,80 @@
|
||||
particlesJS.load('particles-js', 'assets/conf/particlesjs-config.json', function() {
|
||||
console.log('callback - particles.js config loaded');
|
||||
});
|
||||
/*!
|
||||
* Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)
|
||||
* Copyright 2011-2025 The Bootstrap Authors
|
||||
* Licensed under the Creative Commons Attribution 3.0 Unported License.
|
||||
*/
|
||||
|
||||
(() => {
|
||||
'use strict'
|
||||
|
||||
const getStoredTheme = () => localStorage.getItem('theme')
|
||||
const setStoredTheme = theme => localStorage.setItem('theme', theme)
|
||||
|
||||
const getPreferredTheme = () => {
|
||||
const storedTheme = getStoredTheme()
|
||||
if (storedTheme) {
|
||||
return storedTheme
|
||||
}
|
||||
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
const setTheme = theme => {
|
||||
if (theme === 'auto') {
|
||||
document.documentElement.setAttribute('data-bs-theme', (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'))
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-bs-theme', theme)
|
||||
}
|
||||
}
|
||||
|
||||
setTheme(getPreferredTheme())
|
||||
|
||||
const showActiveTheme = (theme, focus = false) => {
|
||||
const themeSwitcher = document.querySelector('#bd-theme')
|
||||
|
||||
if (!themeSwitcher) {
|
||||
return
|
||||
}
|
||||
|
||||
const themeSwitcherText = document.querySelector('#bd-theme-text')
|
||||
const activeThemeIcon = document.querySelector('.theme-icon-active use')
|
||||
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
|
||||
const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href')
|
||||
|
||||
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
|
||||
element.classList.remove('active')
|
||||
element.setAttribute('aria-pressed', 'false')
|
||||
})
|
||||
|
||||
btnToActive.classList.add('active')
|
||||
btnToActive.setAttribute('aria-pressed', 'true')
|
||||
activeThemeIcon.setAttribute('href', svgOfActiveBtn)
|
||||
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
|
||||
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
|
||||
|
||||
if (focus) {
|
||||
themeSwitcher.focus()
|
||||
}
|
||||
}
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
const storedTheme = getStoredTheme()
|
||||
if (storedTheme !== 'light' && storedTheme !== 'dark') {
|
||||
setTheme(getPreferredTheme())
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
showActiveTheme(getPreferredTheme())
|
||||
|
||||
document.querySelectorAll('[data-bs-theme-value]')
|
||||
.forEach(toggle => {
|
||||
toggle.addEventListener('click', () => {
|
||||
const theme = toggle.getAttribute('data-bs-theme-value')
|
||||
setStoredTheme(theme)
|
||||
setTheme(theme)
|
||||
showActiveTheme(theme, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})()
|
||||
Reference in New Issue
Block a user