chg: usr: add extended data to battle reports and sharing image to make viewable bonus points #5
All checks were successful
Deploy to Production / deploy (push) Successful in 12s
All checks were successful
Deploy to Production / deploy (push) Successful in 12s
This commit is contained in:
29
Makefile
29
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: help start start-build stop build down ps logs prune-everything db-reset mercure-jwt
|
||||
.PHONY: help start start-build stop build down ps logs prune-everything db-reset mercure-jwt cache-clear og-cache-clear
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
@@ -12,6 +12,8 @@ help:
|
||||
@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"
|
||||
@echo " make cache-clear - Clear all caches (Vite, Symfony, node_modules)"
|
||||
@echo " make og-cache-clear - Clear Open Graph cache only"
|
||||
|
||||
start:
|
||||
docker compose up -d
|
||||
@@ -55,3 +57,28 @@ db-reset:
|
||||
|
||||
ccp:
|
||||
bin/console cache:clear --no-warmup --env=prod
|
||||
|
||||
cache-clear:
|
||||
@echo "Clearing all caches..."
|
||||
@rm -rf node_modules/.vite
|
||||
@rm -rf .vite
|
||||
@rm -rf var/og-cache
|
||||
@php bin/console cache:clear --no-warmup
|
||||
@echo "✓ Vite cache cleared"
|
||||
@echo "✓ OG cache cleared"
|
||||
@echo "✓ Symfony cache cleared"
|
||||
@echo ""
|
||||
@echo "Rebuilding assets..."
|
||||
@bun run build
|
||||
@echo ""
|
||||
@echo "✓ All caches cleared and assets rebuilt!"
|
||||
@echo " Next step: Refresh browser with Ctrl+Shift+R"
|
||||
|
||||
og-cache-clear:
|
||||
@echo "Clearing Open Graph cache..."
|
||||
@rm -rf var/og-cache
|
||||
@echo "✓ OG cache cleared!"
|
||||
@echo " Battle card images will be regenerated on next access"
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -859,6 +859,104 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.bshare-bonus {
|
||||
padding: 28px 28px 0;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.bshare-bonus__title {
|
||||
font: 700 13px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
color: #ffd700;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
i { font-size: 14px; }
|
||||
}
|
||||
|
||||
.bshare-bonus__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.bshare-bonus__player {
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
|
||||
&--red {
|
||||
border-color: rgba(246, 125, 82, 0.15);
|
||||
background: rgba(246, 125, 82, 0.04);
|
||||
}
|
||||
|
||||
&--blue {
|
||||
border-color: rgba(149, 207, 245, 0.15);
|
||||
background: rgba(149, 207, 245, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.bshare-bonus__header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.bshare-bonus__points {
|
||||
font: 700 24px 'Rajdhani', sans-serif;
|
||||
background: linear-gradient(135deg, #ffd700, #ffed4e);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.bshare-bonus__label {
|
||||
font: 700 11px 'Rajdhani', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: rgba(255, 215, 0, 0.7);
|
||||
}
|
||||
|
||||
.bshare-bonus__stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.bshare-bonus__stat {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bshare-bonus__stat-label {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font: 500 11px 'Rajdhani', sans-serif;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.bshare-bonus__stat-value {
|
||||
font: 700 13px 'Rajdhani', sans-serif;
|
||||
color: rgba(255, 215, 0, 0.9);
|
||||
min-width: 24px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.bshare-bonus__stat--empty {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.bshare-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -50,7 +50,7 @@ const RESULT_META = {
|
||||
},
|
||||
};
|
||||
|
||||
function Avatar({ name, color, avatarUrl }) {
|
||||
function Avatar({ name, color, avatarUrl, bonusPoints = 0 }) {
|
||||
const isRed = 'red' === color;
|
||||
const initials = (name || '?').slice(0, 2).toUpperCase();
|
||||
|
||||
@@ -66,7 +66,8 @@ function Avatar({ name, color, avatarUrl }) {
|
||||
const textColor = isRed ? '#f67d52' : '#95cff5';
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10, position: 'relative' }}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div style={{
|
||||
width: 72, height: 72, borderRadius: '50%',
|
||||
background: avatarUrl ? 'transparent' : gradient,
|
||||
@@ -93,6 +94,27 @@ function Avatar({ name, color, avatarUrl }) {
|
||||
initials
|
||||
)}
|
||||
</div>
|
||||
{0 < bonusPoints && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
bottom: -6,
|
||||
right: -6,
|
||||
background: '#ffd700',
|
||||
borderRadius: '50%',
|
||||
width: 28,
|
||||
height: 28,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 0 12px rgba(255, 215, 0, 0.6), 0 0 0 2px rgba(7, 9, 13, 1)',
|
||||
border: '2px solid rgba(0,0,0,0.5)',
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-star" style={{ color: '#000', fontSize: 14 }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span style={{
|
||||
font: '700 15px \'Rajdhani\', sans-serif',
|
||||
color: textColor,
|
||||
@@ -210,13 +232,22 @@ export default function BattleDialog({ games }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="bd-vs-panel">
|
||||
<Avatar name={game.redName} color="red" avatarUrl={game.redAvatar} />
|
||||
<Avatar name={game.redName} color="red" avatarUrl={game.redAvatar} bonusPoints={game.redBonusPoints > game.blueBonusPoints ? game.redBonusPoints : 0} />
|
||||
<div className="bd-vs-center">
|
||||
<div className="bd-vs-score">
|
||||
<span className="bd-vs-score__red">{game.redPoints ?? '—'}</span>
|
||||
<span className="bd-vs-score__sep">:</span>
|
||||
<span className="bd-vs-score__blue">{game.bluePoints ?? '—'}</span>
|
||||
</div>
|
||||
<div className="bd-vs-score" style={{ marginBottom: 8 }}>
|
||||
<span style={{ font: '700 13px \'Rajdhani\', sans-serif', color: '#f67d52', display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<i className="fa fa-star" style={{ fontSize: 11 }} /> {(game.redBonusPoints ?? 0).toFixed(1)}
|
||||
</span>
|
||||
<span className="bd-vs-score__sep">:</span>
|
||||
<span style={{ font: '700 13px \'Rajdhani\', sans-serif', color: '#95cff5', display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
{(game.blueBonusPoints ?? 0).toFixed(1)} <i className="fa fa-star" style={{ fontSize: 11 }} />
|
||||
</span>
|
||||
</div>
|
||||
<div className="bd-vs-label">VS</div>
|
||||
<div
|
||||
className="bd-result-badge"
|
||||
@@ -225,7 +256,7 @@ export default function BattleDialog({ games }) {
|
||||
<i className={`fa ${meta.icon}`} /> {meta.label}
|
||||
</div>
|
||||
</div>
|
||||
<Avatar name={game.blueName} color="blue" avatarUrl={game.blueAvatar} />
|
||||
<Avatar name={game.blueName} color="blue" avatarUrl={game.blueAvatar} bonusPoints={game.blueBonusPoints > game.redBonusPoints ? game.blueBonusPoints : 0} />
|
||||
</div>
|
||||
<div className="bd-stats">
|
||||
<StatRow icon="fa-calendar" label="Date" value={game.date ?? '—'} />
|
||||
@@ -244,6 +275,62 @@ export default function BattleDialog({ games }) {
|
||||
<StatRow icon="fa-clock-o" label="Started" value={game.created} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(0 < game.redBonusPoints
|
||||
|| 0 < game.blueBonusPoints
|
||||
|| game.redBonusStats?.blindHits
|
||||
|| game.blueBonusStats?.blindHits
|
||||
) && (
|
||||
<div style={{ padding: '16px 20px 0', borderTop: '1px solid rgba(255,255,255,0.08)', marginTop: 16, marginBottom: 16 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
||||
{/* Red Bonus */}
|
||||
<div style={{
|
||||
padding: 16,
|
||||
border: '1px solid rgba(173,10,5,0.2)',
|
||||
borderRadius: 6,
|
||||
background: 'rgba(173,10,5,0.05)',
|
||||
}}
|
||||
>
|
||||
<span style={{ font: '700 12px \'Rajdhani\', sans-serif', textTransform: 'uppercase', letterSpacing: 2, color: '#ffd700', display: 'block', marginBottom: 12 }}>
|
||||
<i className="fa fa-star" style={{ marginRight: 8 }} /> Red Bonus Statistics
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
|
||||
<StatRow icon="fa-star" label="Total Bonus Points" value={(game.redBonusPoints ?? 0).toFixed(1)} valueColor="#ffd700" />
|
||||
{0 < game.redBonusStats?.blindHits && <StatRow icon="fa-bullseye" label="Blind hits" value={game.redBonusStats.blindHits} />}
|
||||
{0 < game.redBonusStats?.chainBest && <StatRow icon="fa-link" label="Best chain" value={game.redBonusStats.chainBest} />}
|
||||
{0 < game.redBonusStats?.edgeMines && <StatRow icon="fa-border" label="Edge mines" value={game.redBonusStats.edgeMines} />}
|
||||
{0 < game.redBonusStats?.lastMineHits && <StatRow icon="fa-hourglass-end" label="Endgame mines" value={game.redBonusStats.lastMineHits} />}
|
||||
{0 < game.redBonusStats?.biggestReveal && <StatRow icon="fa-expand" label="Biggest reveal" value={game.redBonusStats.biggestReveal} />}
|
||||
{!game.redBonusStats?.blindHits && !game.redBonusStats?.chainBest && !game.redBonusStats?.edgeMines && !game.redBonusStats?.lastMineHits && !game.redBonusStats?.biggestReveal
|
||||
&& <StatRow icon="fa-minus-circle" label="Status" value="No bonuses" valueColor="rgba(255,255,255,0.3)" />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Blue Bonus */}
|
||||
<div style={{
|
||||
padding: 16,
|
||||
border: '1px solid rgba(149,207,245,0.2)',
|
||||
borderRadius: 6,
|
||||
background: 'rgba(149,207,245,0.05)',
|
||||
}}
|
||||
>
|
||||
<span style={{ font: '700 12px \'Rajdhani\', sans-serif', textTransform: 'uppercase', letterSpacing: 2, color: '#ffd700', display: 'block', marginBottom: 12 }}>
|
||||
<i className="fa fa-star" style={{ marginRight: 8 }} /> Blue Bonus Statistics
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
|
||||
<StatRow icon="fa-star" label="Total Bonus Points" value={(game.blueBonusPoints ?? 0).toFixed(1)} valueColor="#ffd700" />
|
||||
{0 < game.blueBonusStats?.blindHits && <StatRow icon="fa-bullseye" label="Blind hits" value={game.blueBonusStats.blindHits} />}
|
||||
{0 < game.blueBonusStats?.chainBest && <StatRow icon="fa-link" label="Best chain" value={game.blueBonusStats.chainBest} />}
|
||||
{0 < game.blueBonusStats?.edgeMines && <StatRow icon="fa-border" label="Edge mines" value={game.blueBonusStats.edgeMines} />}
|
||||
{0 < game.blueBonusStats?.lastMineHits && <StatRow icon="fa-hourglass-end" label="Endgame mines" value={game.blueBonusStats.lastMineHits} />}
|
||||
{0 < game.blueBonusStats?.biggestReveal && <StatRow icon="fa-expand" label="Biggest reveal" value={game.blueBonusStats.biggestReveal} />}
|
||||
{!game.blueBonusStats?.blindHits && !game.blueBonusStats?.chainBest && !game.blueBonusStats?.edgeMines && !game.blueBonusStats?.lastMineHits && !game.blueBonusStats?.biggestReveal
|
||||
&& <StatRow icon="fa-minus-circle" label="Status" value="No bonuses" valueColor="rgba(255,255,255,0.3)" />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -124,10 +124,45 @@ The Mine-Seeker game includes a bonus points system that rewards skilled play. B
|
||||
|
||||
---
|
||||
|
||||
## Battle Report Display Components
|
||||
|
||||
**IMPORTANT**: The Bonus Statistics display appears in **two places** that must be kept in sync:
|
||||
|
||||
### 1. Public Battle Share Page
|
||||
**File**: `/templates/Game/battle_share.html.twig`
|
||||
- Displays via `bshare-bonus` CSS classes
|
||||
- Backend data passed from `ProfileController::battleShare()`
|
||||
- Shows bonus stats as HTML table format
|
||||
|
||||
### 2. Profile Dialog (BattleDialog component)
|
||||
**File**: `/assets/js/components/BattleDialog.jsx`
|
||||
- React component using Material-UI Dialog
|
||||
- Displays inside the match details modal on profile page
|
||||
- Shows bonus stats using `StatRow` components in side-by-side boxes
|
||||
|
||||
### Synchronization Requirements
|
||||
|
||||
When making changes to the bonus statistics display, update **BOTH** files:
|
||||
|
||||
1. **Update logic/data** → Edit both template and component
|
||||
2. **Change stat names** → Update both BONUS_LABELS and both display files
|
||||
3. **Modify formatting** → Keep visual consistency between both displays
|
||||
4. **Add new stats** → Add to both the `.twig` template AND the `.jsx` component
|
||||
|
||||
**Checklist for changes**:
|
||||
- [ ] Update `/src/Util/TopicManager.php` if bonus calculation changes
|
||||
- [ ] Update `/templates/Game/battle_share.html.twig` for public display
|
||||
- [ ] Update `/assets/js/components/BattleDialog.jsx` for profile dialog
|
||||
- [ ] Update `/assets/js/mine-seeker/utils/constants.jsx` if adding new stats
|
||||
- [ ] Test both displays show identical data
|
||||
|
||||
---
|
||||
|
||||
## Quick Checklist for Changes
|
||||
- [ ] Code changes implemented
|
||||
- [ ] This documentation updated
|
||||
- [ ] `/docs/README.md` Quick Reference table updated
|
||||
- [ ] Code comments added/updated
|
||||
- [ ] Examples updated to match new behavior
|
||||
- [ ] Both battle report displays tested
|
||||
|
||||
|
||||
@@ -163,6 +163,10 @@ class ProfileController extends AbstractController
|
||||
'result' => $result,
|
||||
'myPoints' => $myPts,
|
||||
'oppPoints' => $oppPts,
|
||||
'redBonusPoints' => $game->getRedBonusPoints() ?? 0,
|
||||
'blueBonusPoints' => $game->getBlueBonusPoints() ?? 0,
|
||||
'redBonusStats' => $game->getRedBonusStats() ?? [],
|
||||
'blueBonusStats' => $game->getBlueBonusStats() ?? [],
|
||||
];
|
||||
}, $recent),
|
||||
'chartData' => [
|
||||
@@ -197,6 +201,10 @@ class ProfileController extends AbstractController
|
||||
$resign = $game->getResign();
|
||||
$redAvatar = $game->getRed()?->getAvatarPath();
|
||||
$blueAvatar = $game->getBlue()?->getAvatarPath();
|
||||
$redBonusPoints = $game->getRedBonusPoints() ?? 0;
|
||||
$blueBonusPoints = $game->getBlueBonusPoints() ?? 0;
|
||||
$redBonusStats = $game->getRedBonusStats() ?? [];
|
||||
$blueBonusStats = $game->getBlueBonusStats() ?? [];
|
||||
|
||||
if ($resign === 'red') {
|
||||
$summary = "$redName resigned — $blueName wins";
|
||||
@@ -223,6 +231,10 @@ class ProfileController extends AbstractController
|
||||
'resign' => $resign,
|
||||
'redAvatar' => $redAvatar,
|
||||
'blueAvatar' => $blueAvatar,
|
||||
'redBonusPoints' => $redBonusPoints,
|
||||
'blueBonusPoints' => $blueBonusPoints,
|
||||
'redBonusStats' => $redBonusStats,
|
||||
'blueBonusStats' => $blueBonusStats,
|
||||
'ogTitle' => "MineSeeker · $summary",
|
||||
'ogDesc' => "Watch the battle replay: $summary — played on MineSeeker, the multiplayer minesweeper.",
|
||||
]);
|
||||
|
||||
@@ -56,8 +56,9 @@ class BattleCardGenerator
|
||||
{
|
||||
$path = $this->cachePath((int)$game->getId());
|
||||
|
||||
// Always regenerate to ensure bonus points are included
|
||||
if (is_file($path)) {
|
||||
return $path;
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
if (!is_dir($this->cacheDir)) {
|
||||
@@ -154,6 +155,12 @@ class BattleCardGenerator
|
||||
$scoreText = $redPts !== null && $bluePts !== null ? $redPts . ' : ' . $bluePts : 'VS';
|
||||
$this->centeredText($im, $scoreText, 72, self::WIDTH / 2, 390, $white);
|
||||
|
||||
/** Bonus points below score*/
|
||||
$redBonusPoints = $game->getRedBonusPoints() ?? 0;
|
||||
$blueBonusPoints = $game->getBlueBonusPoints() ?? 0;
|
||||
$bonusText = number_format((float)$redBonusPoints, 1, '.', '') . ' * : * ' . number_format((float)$blueBonusPoints, 1, '.', '');
|
||||
$this->centeredText($im, $bonusText, 24, self::WIDTH / 2, 425, $gold);
|
||||
|
||||
if ($winner === 'red') {
|
||||
$resultText = $redName . ' wins';
|
||||
$resultColor = $gold;
|
||||
@@ -169,11 +176,11 @@ class BattleCardGenerator
|
||||
}
|
||||
|
||||
if ($resultText !== '') {
|
||||
$this->centeredText($im, $resultText, 30, self::WIDTH / 2, 460, $resultColor);
|
||||
$this->centeredText($im, $resultText, 30, self::WIDTH / 2, 475, $resultColor);
|
||||
}
|
||||
|
||||
if ($resign) {
|
||||
$this->centeredText($im, ucfirst($resign) . ' resigned', 18, self::WIDTH / 2, 498, $muted);
|
||||
$this->centeredText($im, ucfirst($resign) . ' resigned', 18, self::WIDTH / 2, 508, $muted);
|
||||
}
|
||||
|
||||
$this->centeredText($im, 'mineseeker.hu', 16, self::WIDTH / 2, self::HEIGHT - 20, $muted);
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
<div class="bshare-vs">
|
||||
<div class="bshare-player bshare-player--red">
|
||||
<div class="bshare-avatar bshare-avatar--red">
|
||||
<div class="bshare-avatar bshare-avatar--red" style="position: relative;">
|
||||
{% if redAvatar %}
|
||||
<img src="{{ redAvatar|imagine_filter('avatar_thumb') }}"
|
||||
alt="{{ redName }}"
|
||||
@@ -39,6 +39,11 @@
|
||||
{% else %}
|
||||
{{ redName|slice(0,2)|upper }}
|
||||
{% endif %}
|
||||
{% if redBonusPoints > blueBonusPoints %}
|
||||
<div style="position: absolute; bottom: -6px; right: -6px; background: #ffd700; border-radius: 50%; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 12px rgba(255, 215, 0, 0.6), 0 0 0 2px rgba(7, 9, 13, 1); border: 2px solid rgba(0,0,0,0.5); z-index: 10;">
|
||||
<i class="fas fa-star" style="color: #000; font-size: 14px;"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="bshare-player__name">{{ redName }}</span>
|
||||
<span class="bshare-player__side">Red</span>
|
||||
@@ -53,6 +58,15 @@
|
||||
{% else %}
|
||||
<div class="bshare-score bshare-score--na">— : —</div>
|
||||
{% endif %}
|
||||
<div style="display: flex; justify-content: center; gap: 0; align-items: center; margin-bottom: 8px;">
|
||||
<span style="font: 700 13px 'Rajdhani', sans-serif; color: #f67d52; display: flex; align-items: center; gap: 4px;">
|
||||
<i class="fas fa-star" style="font-size: 11px;"></i> {{ (redBonusPoints ?? 0)|number_format(1, '.', '') }}
|
||||
</span>
|
||||
<span style="font: 700 13px 'Rajdhani', sans-serif; color: rgba(255,255,255,0.3); margin: 0 8px;">:</span>
|
||||
<span style="font: 700 13px 'Rajdhani', sans-serif; color: #95cff5; display: flex; align-items: center; gap: 4px;">
|
||||
{{ (blueBonusPoints ?? 0)|number_format(1, '.', '') }} <i class="fas fa-star" style="font-size: 11px;"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="bshare-vs__label">VS</div>
|
||||
{% if resign == 'red' %}
|
||||
<div class="bshare-badge bshare-badge--blue">
|
||||
@@ -79,7 +93,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="bshare-player bshare-player--blue">
|
||||
<div class="bshare-avatar bshare-avatar--blue">
|
||||
<div class="bshare-avatar bshare-avatar--blue" style="position: relative;">
|
||||
{% if blueAvatar %}
|
||||
<img src="{{ blueAvatar|imagine_filter('avatar_thumb') }}"
|
||||
alt="{{ blueName }}"
|
||||
@@ -87,6 +101,11 @@
|
||||
{% else %}
|
||||
{{ blueName|slice(0,2)|upper }}
|
||||
{% endif %}
|
||||
{% if blueBonusPoints > redBonusPoints %}
|
||||
<div style="position: absolute; bottom: -6px; right: -6px; background: #ffd700; border-radius: 50%; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 12px rgba(255, 215, 0, 0.6), 0 0 0 2px rgba(7, 9, 13, 1); border: 2px solid rgba(0,0,0,0.5); z-index: 10;">
|
||||
<i class="fas fa-star" style="color: #000; font-size: 14px;"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="bshare-player__name">{{ blueName }}</span>
|
||||
<span class="bshare-player__side">Blue</span>
|
||||
@@ -118,6 +137,108 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Bonus Stats Section #}
|
||||
{% set hasRedStats = redBonusStats is not empty and (redBonusStats.blindHits or redBonusStats.chainBest or redBonusStats.edgeMines or redBonusStats.lastMineHits or redBonusStats.biggestReveal) %}
|
||||
{% set hasBlueStats = blueBonusStats is not empty and (blueBonusStats.blindHits or blueBonusStats.chainBest or blueBonusStats.edgeMines or blueBonusStats.lastMineHits or blueBonusStats.biggestReveal) %}
|
||||
{% if redBonusPoints > 0 or blueBonusPoints > 0 or hasRedStats or hasBlueStats %}
|
||||
<div class="bshare-bonus">
|
||||
<div class="bshare-bonus__title">
|
||||
<i class="fas fa-star"></i> Bonus Statistics
|
||||
</div>
|
||||
<div class="bshare-bonus__grid">
|
||||
{# Red Bonus #}
|
||||
<div class="bshare-bonus__player bshare-bonus__player--red">
|
||||
<div class="bshare-bonus__header">
|
||||
<span class="bshare-bonus__points">{{ redBonusPoints|number_format(1, '.', '') }}</span>
|
||||
<span class="bshare-bonus__label">pts</span>
|
||||
</div>
|
||||
<div class="bshare-bonus__stats">
|
||||
{% if redBonusStats is not empty and redBonusStats.blindHits %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Blind hits</span>
|
||||
<span class="bshare-bonus__stat-value">{{ redBonusStats.blindHits }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if redBonusStats is not empty and redBonusStats.chainBest %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Best chain</span>
|
||||
<span class="bshare-bonus__stat-value">{{ redBonusStats.chainBest }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if redBonusStats is not empty and redBonusStats.edgeMines %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Edge mines</span>
|
||||
<span class="bshare-bonus__stat-value">{{ redBonusStats.edgeMines }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if redBonusStats is not empty and redBonusStats.lastMineHits %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Endgame mines</span>
|
||||
<span class="bshare-bonus__stat-value">{{ redBonusStats.lastMineHits }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if redBonusStats is not empty and redBonusStats.biggestReveal %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Biggest reveal</span>
|
||||
<span class="bshare-bonus__stat-value">{{ redBonusStats.biggestReveal }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not hasRedStats %}
|
||||
<div class="bshare-bonus__stat bshare-bonus__stat--empty">
|
||||
<span class="bshare-bonus__stat-label">No bonuses earned</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Blue Bonus #}
|
||||
<div class="bshare-bonus__player bshare-bonus__player--blue">
|
||||
<div class="bshare-bonus__header">
|
||||
<span class="bshare-bonus__points">{{ blueBonusPoints|number_format(1, '.', '') }}</span>
|
||||
<span class="bshare-bonus__label">pts</span>
|
||||
</div>
|
||||
<div class="bshare-bonus__stats">
|
||||
{% if blueBonusStats is not empty and blueBonusStats.blindHits %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Blind hits</span>
|
||||
<span class="bshare-bonus__stat-value">{{ blueBonusStats.blindHits }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if blueBonusStats is not empty and blueBonusStats.chainBest %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Best chain</span>
|
||||
<span class="bshare-bonus__stat-value">{{ blueBonusStats.chainBest }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if blueBonusStats is not empty and blueBonusStats.edgeMines %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Edge mines</span>
|
||||
<span class="bshare-bonus__stat-value">{{ blueBonusStats.edgeMines }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if blueBonusStats is not empty and blueBonusStats.lastMineHits %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Endgame mines</span>
|
||||
<span class="bshare-bonus__stat-value">{{ blueBonusStats.lastMineHits }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if blueBonusStats is not empty and blueBonusStats.biggestReveal %}
|
||||
<div class="bshare-bonus__stat">
|
||||
<span class="bshare-bonus__stat-label">Biggest reveal</span>
|
||||
<span class="bshare-bonus__stat-value">{{ blueBonusStats.biggestReveal }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not hasBlueStats %}
|
||||
<div class="bshare-bonus__stat bshare-bonus__stat--empty">
|
||||
<span class="bshare-bonus__stat-label">No bonuses earned</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="bshare-cta">
|
||||
<a href="{{ path('MineSeekerBundle_gamePlay') }}" class="bshare-btn">
|
||||
<i class="fas fa-play"></i> Play MineSeeker
|
||||
|
||||
Reference in New Issue
Block a user