Private
Public Access
1
0

Compare commits

...

4 Commits

Author SHA1 Message Date
247f437445 fix: pkg: the font-awesome simplifying to work on bare-metal - & fix all warnings at build time #4
All checks were successful
Deploy to Production / deploy (push) Successful in 14s
2026-04-18 11:42:46 +02:00
0e94367223 new: usr: add rules page #4 2026-04-18 11:11:52 +02:00
a9ee28b395 fix: usr: the css problem had been solved on reponsive gfx on homepage #4 2026-04-18 10:34:46 +02:00
bd074c5c9d chg: pkg: new version release !skipChangelog 2026-04-18 08:49:59 +02:00
20 changed files with 199 additions and 33 deletions

View File

@@ -1,6 +1,13 @@
# Changelog
## v2026.2.1-8 (2026-04-18)
### Fix
* Quickfix for https-only login - & add user data when the user is not logged in #4. [Lang]
## v2026.2.1-7 (2026-04-16)
### Changes

View File

@@ -11,6 +11,7 @@ help:
@echo " make down - Stop and remove containers/networks"
@echo " make prune-everything - Prune volumes, networks and images (DANGEROUS!)"
@echo " make db-reset - Reset the database (drop, create, migrate) (DANGEROUS!)"
@echo " make ccp - Clear the production cache"
start:
docker compose up -d
@@ -51,3 +52,6 @@ db-reset:
bin/console doctrine:database:drop --force --if-exists --no-interaction
bin/console doctrine:database:create --if-not-exists --no-interaction
bin/console doctrine:migrations:migrate --no-interaction
ccp:
bin/console cache:clear --no-warmup --env=prod

View File

@@ -7,9 +7,4 @@
* file that was distributed with this source code.
*/
$font-path: "/build/webfonts";
@import '@fortawesome/fontawesome-free/scss/fontawesome';
@import '@fortawesome/fontawesome-free/scss/brands';
@import '@fortawesome/fontawesome-free/scss/solid';
@import '@fortawesome/fontawesome-free/scss/regular';
@import '@fortawesome/fontawesome-free/css/all.min.css';

View File

@@ -1,9 +1,10 @@
#hero-auth {
padding: 20px;
.hero-auth {
position: absolute;
top: 28px;
right: 36px;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
z-index: 10;
}
@@ -19,6 +20,13 @@
i { font-size: 15px; }
}
@media screen and (max-width: 1100px) {
.hero-auth {
justify-content: center;
}
}
}
.hero-auth-btn {
display: inline-flex;
align-items: center;

View File

@@ -33,3 +33,11 @@ main div.txt a {
&:hover { color: #c5e8ff; }
}
main div.txt img {
border-radius: 10px;
}
main div.txt .img-container {
text-align: center;
}

View File

@@ -14,7 +14,6 @@ footer {
gap: 40px;
}
// Left: brand block
.footer-brand {
display: flex;
flex-direction: column;
@@ -55,7 +54,6 @@ footer {
line-height: 1.5;
}
// Right: navigation
.footer-nav-label {
font: 700 11px 'Rajdhani', sans-serif;
text-transform: uppercase;
@@ -91,7 +89,6 @@ footer {
}
}
// Bottom copyright bar
.footer-copy {
border-top: 1px solid rgba(255, 255, 255, 0.05);
padding: 16px 60px;

View File

@@ -17,7 +17,6 @@ main {
}
.mine-container {
background: url("/images/bg-mineseeker-0-outbg.jpg") no-repeat;
background-size: cover;
display: flex;
justify-content: center;

View File

@@ -78,8 +78,6 @@
}
#mine-wrapper .grid .field-wrapper .field .field-corner {
background: url('/images/bg-corner-outbg.png') no-repeat top left;
background-size: 100%;
width: 100%;
height: 100%;
}

View File

@@ -320,7 +320,6 @@ footer nav ul li {
}
footer nav ul li:nth-child(even) {
width: 50px;
text-align: center;
}
@@ -401,8 +400,4 @@ footer nav ul li a:hover {
footer nav ul li {
display: block;
}
footer nav ul li:nth-child(even) {
display: none;
}
}

View File

@@ -53,7 +53,10 @@ const GridField = memo(function GridField({ cell, onClick, onMouseEnter }) {
/>
)}
<div className={fieldClass}>
<div className="field-corner">
<div
style={{ background: "url('/images/bg-corner-outbg.png') no-repeat top left / 100% 100%" }}
className="field-corner"
>
{isNaN(currentImage) && (
<div className="flag-mine">
<img src={currentImage} alt="" />

View File

@@ -45,7 +45,7 @@
"scripts": {
"dev": "vite",
"watch": "vite build --watch",
"build": "vite build && cp -r node_modules/@fortawesome/fontawesome-free/webfonts public/build/webfonts",
"build": "vite build",
"lint": "eslint assets/js/"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -111,6 +111,12 @@ class GameController extends AbstractController
return $this->render('Official/landing.html.twig');
}
#[Route('/rules', name: 'MineSeekerBundle_rules')]
public function rules(): Response
{
return $this->render('Official/rules.html.twig');
}
public function sendMail(MailerInterface $mailer, ContactMessage $contactMessage): void
{
try {

View File

@@ -23,9 +23,7 @@
{% endblock %}
{% block header %}
<section
class="hero{% if app.request.attributes.get('_route') != 'MineSeekerBundle_homepage' %} hero--compact{% endif %}">
<section id="hero-auth">
<div class="hero-auth">
{% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
<a href="{{ path('MineSeekerBundle_profile') }}" class="hero-auth-btn hero-auth-btn--profile">
@@ -56,7 +54,10 @@
</a>
{% endif %}
</div>
</section>
<section
class="hero{% if app.request.attributes.get('_route') != 'MineSeekerBundle_homepage' %} hero--compact{% endif %}">
<a class="hero-logo" href="{{ path('MineSeekerBundle_homepage') }}">
<img src="{{ asset('images/mine-logo-txt.png') }}" alt="MineSeeker"/>
</a>
@@ -253,6 +254,7 @@
<p class="footer-nav-label">Navigate</p>
<ul>
<li><a href="{{ path('MineSeekerBundle_homepage') }}">Homepage</a></li>
<li><a href="{{ path('MineSeekerBundle_rules') }}">Game Rules</a></li>
<li><a href="{{ path('MineSeekerBundle_terms') }}">Terms of Use</a></li>
<li><a href="{{ path('MineSeekerBundle_privacy') }}">Privacy Policy</a></li>
<li><a href="{{ path('MineSeekerBundle_contact') }}">Contact</a></li>

View File

@@ -0,0 +1,144 @@
{% extends 'Game/index.html.twig' %}
{% block title %} - Game Rules{% endblock %}
{% block metas %}
{%- set _ogImage = 'https://' ~ app.request.host ~ asset('/images/mine-1600x627.png') -%}
<meta property="og:url" content="{{ url('MineSeekerBundle_rules') | replace({'http://': 'https://'}) }}"/>
<meta property="og:type" content="website"/>
<meta property="og:site_name" content="MineSeeker"/>
<meta property="og:title" content="Game Rules · MineSeeker"/>
<meta property="og:description" content="Learn how to play MineSeeker and discover what you unlock by creating a free account."/>
<meta property="og:image" content="{{ _ogImage }}"/>
<meta property="og:image:width" content="1600"/>
<meta property="og:image:height" content="627"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:title" content="Game Rules · MineSeeker"/>
<meta name="twitter:description" content="Learn how to play MineSeeker and discover what you unlock by creating a free account."/>
<meta name="twitter:image" content="{{ _ogImage }}"/>
{% endblock %}
{% block body %}
<div class="txt">
<h2>MineSeeker Game Rules</h2>
<p>MineSeeker is a real-time 1v1 twist on the classic minesweeper formula. Two players &mdash; <strong>Red</strong> and <strong>Blue</strong> &mdash; race over the same hidden minefield, taking turns to <strong>hunt the mines</strong>. Each mine you detonate is claimed in your colour and scores a point. The first player to claim the majority of the mines wins.</p>
<h3>1. The Board</h3>
<ul>
<li>The playing field is a <strong>16&times;16 grid</strong> of covered cells.</li>
<li><strong>51 mines</strong> are hidden randomly across the board at the start of each match.</li>
<li>Every non-mine cell displays a number indicating how many of its eight neighbours contain a mine &mdash; these numbers are your clues. Cells with no adjacent mines are empty.</li>
</ul>
<h3>2. Turn Order</h3>
<ul>
<li>Players alternate turns. On your turn the status bar reads <em>&ldquo;It is your turn! Make a move&rdquo;</em>; while you wait it reads <em>&ldquo;Your buddy is making a move&rdquo;</em>.</li>
<li>On your turn you must perform exactly one action: reveal a cell, flag/unflag a cell, or deploy your bomb.</li>
<li><strong>If you hit a mine, you keep your turn</strong> and may click again. Your turn only ends when you reveal a safe cell (or use your bomb).</li>
</ul>
<h3>3. Revealing Cells</h3>
<ul>
<li><strong>Left-click</strong> a covered cell to reveal it.</li>
<li>Revealing a <strong>numbered cell</strong> just uncovers the clue &mdash; no points are awarded &mdash; and your turn ends.</li>
<li>Revealing an <strong>empty cell</strong> triggers a <strong>flood-fill</strong> that opens all connected empty cells and their numbered borders in a single move. Flood-fill will never step onto a mine, so empty-area sweeps are always safe.</li>
<li><strong>Right-click</strong> a covered cell to place a flag where you suspect a mine. Flagged cells cannot be revealed until unflagged.</li>
</ul>
<h3>4. Claiming Mines &amp; Scoring</h3>
<ul>
<li>Clicking a mine is the <strong>goal</strong> of the game, not a failure. The mine is marked with your colour&rsquo;s flag and scores <strong>one point</strong> for you.</li>
<li>You keep the turn and may click again &mdash; rack up a streak while you&rsquo;re hot.</li>
<li>Your turn only ends when you finally reveal a safe cell (or deploy your bomb).</li>
</ul>
<h3>5. The Bomb</h3>
<ul>
<li>Each player carries <strong>one bomb</strong> per match.</li>
<li>Detonating your bomb clears a <strong>5&times;5 blast radius</strong> (25 cells) around the target. <strong>Every mine inside the radius is claimed for your colour and adds to your score.</strong> Numbered cells in the radius are also revealed.</li>
<li>The bomb consumes your turn and can only be used once. Save it for a dense patch of suspected mines to burst ahead on the scoreboard.</li>
</ul>
<h3>6. Winning the Match</h3>
<p>A match ends in one of three ways:</p>
<ul>
<li><strong>Majority reached</strong> &mdash; the first player to claim more than half of the mines (26 of 51) wins immediately.</li>
<li><strong>A player resigns</strong> &mdash; the remaining player wins.</li>
<li><strong>Draw</strong> &mdash; if neither player reaches the majority and scores end up equal, the match is recorded as a draw.</li>
</ul>
<h3>7. Playing as a Guest</h3>
<p>No account is required to play. Just open the game, share the match link with a friend, and play. Guest matches are not saved to a history and carry no stats.</p>
<h2 style="margin-top: 40px;">Registered User Privileges</h2>
<p>Creating a free account unlocks everything the guest experience leaves behind. Registration takes under a minute and your email is only used for account recovery.</p>
<h3>1. Persistent Game History</h3>
<ul>
<li>Every match you play is recorded with timestamps, the full move list, the final grid, and your opponent&rsquo;s name.</li>
<li>Replay past battles cell-by-cell and share them with a public UUID link so friends can watch your finest detonations.</li>
</ul>
<div class="img-container">
<img style="margin-top: 15px;" src="{{ asset('images/privileges/history.png') }}" alt="Recent Game History" />
</div>
<h3>2. Player Statistics</h3>
<ul>
<li>Total games, wins, losses, and draws.</li>
<li>Win rate percentage, average score, personal best score, and total mines hit.</li>
<li>A 6-month trend dashboard charting wins, losses, and draws per month.</li>
</ul>
<div class="img-container">
<img style="margin-top: 15px;" src="{{ asset('images/privileges/stat.png') }}" alt="Statistics" />
</div>
<h3>3. Profile &amp; Identity</h3>
<ul>
<li>Upload a custom <strong>avatar</strong> that appears next to your username on the board and in the shared battles.</li>
<li>Your username is reserved &mdash; no one else can take it.</li>
</ul>
<h3>4. Account Security</h3>
<ul>
<li><strong>Two-factor authentication (TOTP):</strong> Protect your account with an authenticator app and a set of one-time backup codes.</li>
<li><strong>WebAuthn passkeys:</strong> Register one or more hardware/biometric security keys for passwordless sign-in.</li>
<li>A dedicated <strong>Security</strong> dashboard to manage backup codes, review registered credentials, and rotate them at any time.</li>
</ul>
<div class="img-container">
<img style="margin-top: 15px;" src="{{ asset('images/privileges/security.png') }}" alt="Security Dashboard" />
</div>
<h3>5. Shareable Battle Pages</h3>
<ul>
<li>Each recorded match gets a public page with both players&rsquo; names, avatars, final scores, the outcome, and a compact summary of how it played out.</li>
<li>Perfect for proving that impossible last-turn comeback.</li>
</ul>
<div class="img-container">
<img style="margin-top: 15px;" src="{{ asset('images/privileges/battle.png') }}" alt="Shareable Battle Pages" />
</div>
<div class="img-container">
<img style="margin-top: 15px;" src="{{ asset('images/privileges/shared-battle.png') }}" alt="Shared Battle Page" />
</div>
<p style="margin-top: 32px;">Ready to level up? <a href="{{ path('MineSeekerBundle_register') }}">Create your free account</a> or <a href="{{ path('MineSeekerBundle_gamePlay') }}">jump straight into a match</a>.</p>
</div>
{% endblock %}