Complete layout overhaul

This commit is contained in:
Frank
2026-07-04 19:08:31 +02:00
parent 05f4f2c32f
commit 5b03bd1d1c
22 changed files with 842 additions and 175 deletions
+4 -3
View File
@@ -1,6 +1,7 @@
/*
* Welcome to your app's main JavaScript file!
*/
import './styles/app.css';
console.log('This log comes from assets/app.js built by Webpack Encore! 🎉');
import './styles/app.scss';
import 'bootstrap/js/dist/collapse';
import 'bootstrap/js/dist/alert';
import 'bootstrap/js/dist/dropdown';
-3
View File
@@ -1,3 +0,0 @@
body {
background-color: skyblue;
}
+66
View File
@@ -0,0 +1,66 @@
// Brand palette derived from public/images/logo.png (teal key/globe on dark navy)
$brand-teal: #2f8fae;
$brand-teal-dark: #1c5a70;
$brand-teal-light:#6fb8d1;
$brand-navy: #0d1f26;
$brand-navy-soft: #16323f;
$brand-bg: #f4f8f9;
// Bootstrap variable overrides (must come before the bootstrap import)
$primary: $brand-teal;
$secondary: $brand-navy-soft;
$dark: $brand-navy;
$body-bg: $brand-bg;
$body-color: $brand-navy-soft;
$link-color: $brand-teal;
$link-hover-color: $brand-teal-dark;
$border-radius: .5rem;
@import "bootstrap/scss/bootstrap";
@import "bootstrap-icons/font/bootstrap-icons.css";
:root {
--brand-teal: #{$brand-teal};
--brand-teal-dark: #{$brand-teal-dark};
--brand-navy: #{$brand-navy};
--brand-navy-soft: #{$brand-navy-soft};
--brand-bg: #{$brand-bg};
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.site-main {
flex: 1 0 auto;
}
.site-header {
background: linear-gradient(90deg, $brand-navy, $brand-navy-soft);
}
.site-header .navbar-brand {
display: flex;
align-items: center;
gap: .5rem;
font-weight: 700;
letter-spacing: .02em;
}
.site-header .navbar-brand img {
height: 36px;
width: auto;
}
.site-footer {
flex-shrink: 0;
background: $brand-navy;
color: rgba(255, 255, 255, .65);
}
.auth-card {
max-width: 440px;
margin: 3rem auto;
}
+1
View File
@@ -1,4 +1,5 @@
twig:
form_themes: ['bootstrap_5_layout.html.twig']
globals:
mercure_public_url: '%env(MERCURE_PUBLIC_URL)%'
mercure_topic_base: '%env(MERCURE_TOPIC_BASE)%'
+494
View File
@@ -8,6 +8,11 @@
"name": "escapepage",
"version": "1.0.0",
"license": "UNLICENSED",
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1"
},
"devDependencies": {
"@babel/core": "^7.25.0",
"@babel/preset-env": "^7.25.0",
@@ -17,6 +22,8 @@
"css-loader": "^7.1.2",
"mini-css-extract-plugin": "^2.9.2",
"regenerator-runtime": "^0.14.1",
"sass": "^1.101.0",
"sass-loader": "^14.2.1",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"webpack-notifier": "^1.15.0"
@@ -1743,6 +1750,340 @@
"node": ">=4"
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
"node-addon-api": "^7.0.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.6",
"@parcel/watcher-darwin-arm64": "2.5.6",
"@parcel/watcher-darwin-x64": "2.5.6",
"@parcel/watcher-freebsd-x64": "2.5.6",
"@parcel/watcher-linux-arm-glibc": "2.5.6",
"@parcel/watcher-linux-arm-musl": "2.5.6",
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
"@parcel/watcher-linux-arm64-musl": "2.5.6",
"@parcel/watcher-linux-x64-glibc": "2.5.6",
"@parcel/watcher-linux-x64-musl": "2.5.6",
"@parcel/watcher-win32-arm64": "2.5.6",
"@parcel/watcher-win32-ia32": "2.5.6",
"@parcel/watcher-win32-x64": "2.5.6"
}
},
"node_modules/@parcel/watcher-android-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher/node_modules/picomatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -2884,6 +3225,41 @@
"dev": true,
"license": "ISC"
},
"node_modules/bootstrap": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bootstrap-icons": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -3718,6 +4094,17 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/detect-node": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
@@ -4777,6 +5164,13 @@
"postcss": "^8.1.0"
}
},
"node_modules/immutable": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz",
"integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==",
"dev": true,
"license": "MIT"
},
"node_modules/import-local": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
@@ -5554,6 +5948,14 @@
"dev": true,
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/node-forge": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz",
@@ -6861,6 +7263,98 @@
"dev": true,
"license": "MIT"
},
"node_modules/sass": {
"version": "1.101.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.101.0.tgz",
"integrity": "sha512-OL3GoQyoUdDt843DpVmDO6y2k1sc5IhUDSpu8XucEI+35neq5QivZ1iuegnpraEVTJXlQGK1gl27zKcTLEPbQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^5.0.0",
"immutable": "^5.1.5",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=20.19.0"
},
"optionalDependencies": {
"@parcel/watcher": "^2.4.1"
}
},
"node_modules/sass-loader": {
"version": "14.2.1",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.1.tgz",
"integrity": "sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"neo-async": "^2.6.2"
},
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"@rspack/core": "0.x || 1.x",
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
"sass": "^1.3.0",
"sass-embedded": "*",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"@rspack/core": {
"optional": true
},
"node-sass": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"webpack": {
"optional": true
}
}
},
"node_modules/sass/node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^5.0.0"
},
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/sass/node_modules/readdirp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/schema-utils": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
+7
View File
@@ -18,8 +18,15 @@
"css-loader": "^7.1.2",
"mini-css-extract-plugin": "^2.9.2",
"regenerator-runtime": "^0.14.1",
"sass": "^1.101.0",
"sass-loader": "^14.2.1",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"webpack-notifier": "^1.15.0"
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1"
}
}
+4 -2
View File
@@ -5,14 +5,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ 'site.name'|trans }}{% endblock %}</title>
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{{ encore_entry_link_tags('app') }}
{% endblock %}
</head>
<body>
{% block header %}
<header>
<nav>
{% set pathinfo = app.request.pathinfo %}
<a href="/">{{ 'nav.home'|trans }}</a> |
<a href="{{ path('game_dashboard') }}">{{ 'nav.game'|trans }}</a> |
{% if app.user %}
@@ -23,6 +22,7 @@
{% endif %}
</nav>
</header>
{% endblock %}
{% block main %}
<main>
{% for label, messages in app.flashes %}
@@ -35,9 +35,11 @@
{% block body %}{% endblock %}
</main>
{% endblock %}
{% block footer %}
<footer>
<small>&copy; {{ "now"|date("Y") }} {{ 'site.name'|trans }}</small>
</footer>
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
+57 -41
View File
@@ -1,42 +1,57 @@
{% extends 'base.html.twig' %}
{% extends 'layout/site.html.twig' %}
{% block title %}Game Dashboard{% endblock %}
{% block body %}
<h1>Game Dashboard</h1>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="mb-0">Game Dashboard</h1>
{% if is_granted('ROLE_ADMIN') %}
<a href="{{ path('game_admin_dashboard') }}" class="btn btn-outline-secondary">Go to Game Admin Dashboard</a>
{% endif %}
</div>
{% if is_granted('ROLE_ADMIN') %}
<p><a href="{{ path('game_admin_dashboard') }}">Go to Game Admin Dashboard</a></p>
{% endif %}
<div class="row g-4 mb-4">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-secondary text-white">Create New Session</div>
<div class="card-body">
{% if availableGames is not empty %}
<form method="post">
<select name="game_id" class="form-select mb-3">
{% for game in availableGames %}
<option value="{{ game.id }}">
{{ game.name }} ({{ game.numberOfPlayers }} players)
{% if is_granted('ROLE_ADMIN') %}
[{{ game.status.value }}]
{% endif %}
</option>
{% endfor %}
</select>
<button type="submit" name="create_session" class="btn btn-primary">Create Session</button>
</form>
{% else %}
<p class="text-muted mb-0">No games available to start.</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-secondary text-white">Join Session</div>
<div class="card-body">
<form method="post" class="d-flex gap-2">
<input type="text" name="invite_code" class="form-control" placeholder="Enter Invite Code" required>
<button type="submit" name="join_session" class="btn btn-primary text-nowrap">Join Session</button>
</form>
</div>
</div>
</div>
</div>
<h2>Create New Session</h2>
{% if availableGames is not empty %}
<form method="post">
<select name="game_id">
{% for game in availableGames %}
<option value="{{ game.id }}">
{{ game.name }} ({{ game.numberOfPlayers }} players)
{% if is_granted('ROLE_ADMIN') %}
[{{ game.status.value }}]
{% endif %}
</option>
{% endfor %}
</select>
<button type="submit" name="create_session">Create Session</button>
</form>
{% else %}
<p>No games available to start.</p>
{% endif %}
<h2>Join Session</h2>
<form method="post">
<input type="text" name="invite_code" placeholder="Enter Invite Code" required>
<button type="submit" name="join_session">Join Session</button>
</form>
<h2>Your Sessions</h2>
<h2 class="h4">Your Sessions</h2>
{% if sessions is not empty %}
<table>
<div class="table-responsive">
<table class="table table-striped align-middle">
<thead>
<tr>
<th>ID</th>
@@ -52,7 +67,7 @@
<tr>
<td>{{ session.id }}</td>
<td>{{ session.game.name }}</td>
<td>{{ session.status.value }}</td>
<td><span class="badge bg-info text-dark">{{ session.status.value }}</span></td>
<td>{{ session.created|date('Y-m-d H:i') }}</td>
<td>
{% set inviteCode = '' %}
@@ -65,25 +80,25 @@
{% if inviteCode %}
<code>{{ inviteCode }}</code>
{% else %}
<form method="post" style="display:inline;">
<form method="post" class="d-inline">
<input type="hidden" name="session_id" value="{{ session.id }}">
<button type="submit" name="create_invite">Generate Invite</button>
<button type="submit" name="create_invite" class="btn btn-sm btn-outline-secondary">Generate Invite</button>
</form>
{% endif %}
</td>
<td>
<a href="{{ path('game', {session: session.id}) }}">Enter Game</a>
<a href="{{ path('game', {session: session.id}) }}" class="btn btn-sm btn-primary">Enter Game</a>
{% if session.status.value == 'created' %}
{% if session.players|length >= session.game.numberOfPlayers %}
<form method="post" style="display:inline;">
<form method="post" class="d-inline">
<input type="hidden" name="session_id" value="{{ session.id }}">
<button type="submit" name="start_session">Start Session</button>
<button type="submit" name="start_session" class="btn btn-sm btn-success">Start Session</button>
</form>
{% endif %}
{% if session.timer == 0 %}
<form method="post" style="display:inline;">
<form method="post" class="d-inline">
<input type="hidden" name="session_id" value="{{ session.id }}">
<button type="submit" name="leave_session" onclick="return confirm('Are you sure you want to leave this session?')">Leave Session</button>
<button type="submit" name="leave_session" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to leave this session?')">Leave Session</button>
</form>
{% endif %}
{% endif %}
@@ -92,7 +107,8 @@
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p>You are not part of any sessions.</p>
<p class="text-muted">You are not part of any sessions.</p>
{% endif %}
{% endblock %}
+5 -7
View File
@@ -1,11 +1,11 @@
{% extends 'base.html.twig' %}
{% extends 'layout/site.html.twig' %}
{% block title %}Game Lost - {{ session.game.name }}{% endblock %}
{% block stylesheets %}
{{ parent() }}
<style>
body {
.site-main {
background-color: #1a1a1a;
color: #e0e0e0;
}
@@ -46,10 +46,9 @@
{% endblock %}
{% block body %}
<div class="container mt-5 mb-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg">
<div class="card-header bg-danger text-white">
<h3 class="card-title mb-0">Game Over - Time's Up!</h3>
</div>
@@ -123,5 +122,4 @@
</div>
</div>
</div>
</div>
{% endblock %}
+62 -64
View File
@@ -1,77 +1,75 @@
{% extends 'base.html.twig' %}
{% extends 'layout/site.html.twig' %}
{% block title %}Waiting for players - {{ session.game.name }}{% endblock %}
{% block body %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h3 class="card-title mb-0">Waiting for all players to be ready</h3>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h3 class="card-title mb-0">Waiting for all players to be ready</h3>
</div>
<div class="card-body">
<h4>{{ session.game.name }}</h4>
<p>Welcome to the game! Please wait for all players to join and signal they are ready to start.</p>
<div class="game-info">
Please keep the following things in mind:
<ul>
<li>This game is best played in full screen mode. For windows, press F11. For Mac, press Cmd+Ctrl+F.</li>
<li>There is no need to reload the page. There is even a chance this could break the game.</li>
<li>If your internet connection is lost, you can get back in the game after internet has been fixed.</li>
</ul>
</div>
<div class="card-body">
<h4>{{ session.game.name }}</h4>
<p>Welcome to the game! Please wait for all players to join and signal they are ready to start.</p>
<div class="game-info">
Please keep the following things in mind:
<ul>
<li>This game is best played in full screen mode. For windows, press F11. For Mac, press Cmd+Ctrl+F.</li>
<li>There is no need to reload the page. There is even a chance this could break the game.</li>
<li>If your internet connection is lost, you can get back in the game after internet has been fixed.</li>
</ul>
</div>
<div class="alert alert-info">
<strong>Game Information:</strong>
<ul class="mb-0">
<li>Number of players: {{ session.game.numberOfPlayers }}</li>
<li>Current players: {{ session.players|length }} / {{ session.game.numberOfPlayers }}</li>
</ul>
</div>
<div class="alert alert-info">
<strong>Game Information:</strong>
<ul class="mb-0">
<li>Number of players: {{ session.game.numberOfPlayers }}</li>
<li>Current players: {{ session.players|length }} / {{ session.game.numberOfPlayers }}</li>
</ul>
</div>
<div class="mt-4">
<h5>Players status:</h5>
<ul class="list-group">
{% for player in session.players %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ player.user.username }}
{% set playerReady = false %}
{% set settingName = 'ReadyAtForPlayer' ~ player.screen %}
{% for setting in session.settings %}
{% if setting.name.value == settingName and setting.player == player %}
{% set playerReady = true %}
{% endif %}
{% endfor %}
{% if playerReady %}
<span class="badge bg-success">Ready</span>
{% else %}
<span class="badge bg-secondary">Not ready</span>
<div class="mt-4">
<h5>Players status:</h5>
<ul class="list-group">
{% for player in session.players %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ player.user.username }}
{% set playerReady = false %}
{% set settingName = 'ReadyAtForPlayer' ~ player.screen %}
{% for setting in session.settings %}
{% if setting.name.value == settingName and setting.player == player %}
{% set playerReady = true %}
{% endif %}
</li>
{% endfor %}
</ul>
{% endfor %}
{% if playerReady %}
<span class="badge bg-success">Ready</span>
{% else %}
<span class="badge bg-secondary">Not ready</span>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
<hr>
<form method="post" class="mt-4">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="toggle_ready" name="toggle_ready" onchange="this.form.submit()" {{ isReady ? 'checked' : '' }}>
<label class="form-check-label" for="toggle_ready">
<strong>I am ready to start!</strong>
</label>
</div>
<p class="text-muted small">
Note: If all players are not ready within 1 minute of you checking this, your status will be reset automatically.
</p>
</form>
<hr>
<form method="post" class="mt-4">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="toggle_ready" name="toggle_ready" onchange="this.form.submit()" {{ isReady ? 'checked' : '' }}>
<label class="form-check-label" for="toggle_ready">
<strong>I am ready to start!</strong>
</label>
</div>
<p class="text-muted small">
Note: If all players are not ready within 1 minute of you checking this, your status will be reset automatically.
</p>
</form>
<div class="mt-3">
<a href="{{ path('game_dashboard') }}" class="btn btn-outline-secondary">Back to Dashboard</a>
</div>
<div class="mt-3">
<a href="{{ path('game_dashboard') }}" class="btn btn-outline-secondary">Back to Dashboard</a>
</div>
</div>
</div>
+12
View File
@@ -0,0 +1,12 @@
{% extends 'layout/site.html.twig' %}
{% block body %}
<div class="auth-card">
<div class="card shadow-sm">
<div class="card-body p-4 p-md-5">
<h1 class="h3 mb-4 text-center">{{ block('title') }}</h1>
{% block auth_body %}{% endblock %}
</div>
</div>
</div>
{% endblock %}
+64
View File
@@ -0,0 +1,64 @@
{% extends 'base.html.twig' %}
{% block header %}
<header class="site-header">
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container">
<a class="navbar-brand" href="{{ path('website_home') }}">
<img src="{{ asset('images/logo.png') }}" alt="{{ 'site.name'|trans }}">
{{ 'site.name'|trans }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#siteNav"
aria-controls="siteNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="siteNav">
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
<li class="nav-item">
<a class="nav-link" href="{{ path('website_home') }}">{{ 'nav.home'|trans }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ path('game_dashboard') }}">{{ 'nav.game'|trans }}</a>
</li>
{% if app.user %}
<li class="nav-item">
<a class="nav-link" href="{{ path('app_logout') }}">{{ 'nav.logout'|trans }}</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ path('app_register') }}">{{ 'nav.register'|trans }}</a>
</li>
<li class="nav-item">
<a class="btn btn-sm btn-outline-light ms-lg-2" href="{{ path('app_login') }}">{{ 'nav.login'|trans }}</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
</header>
{% endblock %}
{% block main %}
<main class="site-main">
<div class="container py-4">
{% for label, messages in app.flashes %}
{% for message in messages %}
<div class="alert alert-{{ label }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endfor %}
{% block body %}{% endblock %}
</div>
</main>
{% endblock %}
{% block footer %}
<footer class="site-footer py-4">
<div class="container text-center small">
&copy; {{ "now"|date("Y") }} {{ 'site.name'|trans }}
</div>
</footer>
{% endblock %}
@@ -1,10 +1,8 @@
{% extends 'base.html.twig' %}
{% extends 'layout/auth.html.twig' %}
{% block title %}Register{% endblock %}
{% block body %}
<h1>Register</h1>
{% block auth_body %}
{{ form_errors(registrationForm) }}
{{ form_start(registrationForm) }}
@@ -13,6 +11,12 @@
{{ form_row(registrationForm.plainPassword) }}
{{ form_row(registrationForm.captcha) }}
<button type="submit" class="btn">Register</button>
<div class="d-grid mt-3">
<button type="submit" class="btn btn-primary">Register</button>
</div>
{{ form_end(registrationForm) }}
<div class="mt-3 text-center">
<a href="{{ path('app_login') }}">Already have an account? Log in</a>
</div>
{% endblock %}
@@ -1,17 +1,17 @@
{% extends 'base.html.twig' %}
{% extends 'layout/auth.html.twig' %}
{% block title %}Resend verification email{% endblock %}
{% block body %}
<h1>Resend verification email</h1>
<p>Enter your email address and we will send you a new link to verify your account.</p>
{% block auth_body %}
<p class="text-muted">Enter your email address and we will send you a new link to verify your account.</p>
{{ form_errors(resendForm) }}
{{ form_start(resendForm) }}
{{ form_row(resendForm.email) }}
<button type="submit" class="btn">Resend email</button>
<div class="d-grid mt-3">
<button type="submit" class="btn btn-primary">Resend email</button>
</div>
{{ form_end(resendForm) }}
{% endblock %}
@@ -1,11 +1,11 @@
{% extends 'base.html.twig' %}
{% extends 'layout/auth.html.twig' %}
{% block title %}Password Reset Email Sent{% endblock %}
{% block body %}
{% block auth_body %}
<p>
If an account matching your email exists, then an email was just sent that contains a link that you can use to reset your password.
This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.
</p>
<p>If you don't receive an email please check your spam folder or <a href="{{ path('app_forgot_password_request') }}">try again</a>.</p>
<p class="mb-0">If you don't receive an email please check your spam folder or <a href="{{ path('app_forgot_password_request') }}">try again</a>.</p>
{% endblock %}
@@ -1,22 +1,20 @@
{% extends 'base.html.twig' %}
{% extends 'layout/auth.html.twig' %}
{% block title %}Reset your password{% endblock %}
{% block body %}
{% block auth_body %}
{% for flash_error in app.flashes('reset_password_error') %}
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
{% endfor %}
<h1>Reset your password</h1>
{{ form_start(requestForm) }}
{{ form_row(requestForm.email) }}
<div>
<small>
Enter your email address, and we will send you a
link to reset your password.
</small>
</div>
<p class="text-muted small">
Enter your email address, and we will send you a link to reset your password.
</p>
<button class="btn btn-primary">Send password reset email</button>
<div class="d-grid">
<button class="btn btn-primary">Send password reset email</button>
</div>
{{ form_end(requestForm) }}
{% endblock %}
@@ -1,12 +1,12 @@
{% extends 'base.html.twig' %}
{% extends 'layout/auth.html.twig' %}
{% block title %}Reset your password{% endblock %}
{% block body %}
<h1>Reset your password</h1>
{% block auth_body %}
{{ form_start(resetForm) }}
{{ form_row(resetForm.plainPassword) }}
<button class="btn btn-primary">Reset password</button>
<div class="d-grid mt-3">
<button class="btn btn-primary">Reset password</button>
</div>
{{ form_end(resetForm) }}
{% endblock %}
+21 -21
View File
@@ -1,8 +1,8 @@
{% extends 'base.html.twig' %}
{% extends 'layout/auth.html.twig' %}
{% block title %}Log in!{% endblock %}
{% block title %}Log in{% endblock %}
{% block body %}
{% block auth_body %}
<form method="post">
{% if error %}
<div class="alert alert-danger">
@@ -14,39 +14,39 @@
{% endif %}
{% if app.user %}
<div class="mb-3">
<div class="alert alert-info">
You are logged in as {{ app.user.userIdentifier }}, <a href="{{ path('app_logout') }}">Logout</a>
</div>
{% endif %}
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="inputUsername">Username</label>
<input type="text" value="{{ last_username }}" name="username" id="inputUsername" class="form-control" autocomplete="username" required autofocus>
<label for="inputPassword">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" autocomplete="current-password" required>
<div class="mb-3">
<label for="inputUsername" class="form-label">Username</label>
<input type="text" value="{{ last_username }}" name="username" id="inputUsername" class="form-control" autocomplete="username" required autofocus>
</div>
<div class="mb-3">
<label for="inputPassword" class="form-label">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" autocomplete="current-password" required>
</div>
<input type="hidden" name="_csrf_token"
value="{{ csrf_token('authenticate') }}"
>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
{#
Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
See https://symfony.com/doc/current/security/remember_me.html
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="_remember_me"> Remember me
</label>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="rememberMe" name="_remember_me">
<label class="form-check-label" for="rememberMe">Remember me</label>
</div>
#}
<button class="btn btn-lg btn-primary" type="submit">
Sign in
</button>
<div class="mt-3">
<div class="d-grid">
<button class="btn btn-primary" type="submit">Sign in</button>
</div>
<div class="mt-3 text-center">
<a href="{{ path('app_forgot_password_request') }}">Forgot your password?</a>
</div>
<div class="mt-1">
<div class="mt-1 text-center">
<a href="{{ path('app_verify_resend_email') }}">Didn't receive activation email?</a>
</div>
</form>
+7 -4
View File
@@ -1,9 +1,12 @@
{% extends 'base.html.twig' %}
{% extends 'layout/site.html.twig' %}
{% block title %}{{ 'home.title'|trans({'%site%': ('site.name'|trans)}) }}{% endblock %}
{% block body %}
<h1>{{ 'home.h1'|trans({'%site%': ('site.name'|trans)}) }}</h1>
<p>{{ 'home.description'|trans }}</p>
<p><a href="{{ path('game_dashboard') }}">{{ 'link.enter_game'|trans }}</a></p>
<div class="text-center py-5">
<img src="{{ asset('images/logo.png') }}" alt="{{ 'site.name'|trans }}" height="140" class="mb-4">
<h1 class="display-5 fw-bold">{{ 'home.h1'|trans({'%site%': ('site.name'|trans)}) }}</h1>
<p class="lead col-lg-8 mx-auto text-secondary">{{ 'home.description'|trans }}</p>
<a href="{{ path('game_dashboard') }}" class="btn btn-primary btn-lg mt-3">{{ 'link.enter_game'|trans }}</a>
</div>
{% endblock %}
+3
View File
@@ -2,6 +2,9 @@
site.name: EscapePage
nav.home: Home
nav.game: Game
nav.login: Login
nav.register: Register
nav.logout: Logout
link.enter_game: Enter the Game Area
link.back_to_website: Back to Website
+3
View File
@@ -2,6 +2,9 @@
site.name: EscapePage
nav.home: Start
nav.game: Spel
nav.login: Inloggen
nav.register: Registreren
nav.logout: Uitloggen
link.enter_game: Ga naar het Speelgedeelte
link.back_to_website: Terug naar Website
+1 -1
View File
@@ -21,13 +21,13 @@ Encore
// features
.enableSourceMaps(!Encore.isProduction())
.enableVersioning(Encore.isProduction())
.enableSassLoader()
// Babel config for wide browser support
.configureBabelPresetEnv((options) => {
options.useBuiltIns = 'usage';
options.corejs = 3;
options.bugfixes = true;
})
.autoProvidejQuery(false)
.enableBuildNotifications()
;