Commit f04a3f66 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: Phase 2+3 UI/UX — animations, sounds, interactions, polish

Phase 2 (P1):
- Pull-to-refresh on mobile (touch gesture + indicator)
- Bottom sheet modal system (App.showSheet/hideSheet)
- Enhanced toasts with icons, progress bar, swipe-to-dismiss
- Nav sliding active indicator (vertical bar desktop, horizontal mobile)
- Counter/number animations on profile load
- Request caching (App.cachedFetch with 30s TTL)
- Responsive typography scale (clamp-based)
- Safe area handling for all edges
- Animated gradient background on Games Hub
- Elevation/depth system (4 levels)
- Ludo capture explosion particles
- Sound feedback system (Web Audio synthesized tones)
- Win streak fire badge on profile nav
- Landscape game layout adjustments
- Gold shimmer sweep on .btn-gold

Phase 3 (P2):
- 3D dice roll animation (perspective + rotateX/Y)
- Achievement celebration overlay (confetti + gold title)
- Gold shimmer effect utility class
- Clock urgency visual (pulse <30s, shake <10s)
- Page prefetching for adjacent routes
- Soft navigation helper (App.navigate with exit animation)
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent dcec48a9
...@@ -48,5 +48,19 @@ if (window.__themeAssets) { ...@@ -48,5 +48,19 @@ if (window.__themeAssets) {
<script src="<?= $extraJs ?>"></script> <script src="<?= $extraJs ?>"></script>
<?php endif; ?> <?php endif; ?>
<?php endif; ?> <?php endif; ?>
<?php
// Prefetch adjacent pages
$prefetchMap = [
'' => ['/games', '/profile'],
'home' => ['/games', '/profile'],
'games' => ['/play', '/ludo'],
'play' => ['/games', '/game'],
'ludo' => ['/games', '/ludo-game'],
];
$currentRoute = $route ?? '';
if (isset($prefetchMap[$currentRoute])):
foreach ($prefetchMap[$currentRoute] as $pf): ?>
<link rel="prefetch" href="<?= $pf ?>">
<?php endforeach; endif; ?>
</body> </body>
</html> </html>
<?php $pageTitle = 'EL3AB - العاب'; ?> <?php $pageTitle = 'EL3AB - العاب'; ?>
<?php require __DIR__ . '/../includes/header.php'; ?> <?php require __DIR__ . '/../includes/header.php'; ?>
<div class="space-y-6" id="games-content"> <div class="space-y-6 bg-animated" id="games-content">
<h2 class="page-title">العاب</h2> <h2 class="page-title">العاب</h2>
......
...@@ -149,6 +149,7 @@ function saveSettings() { ...@@ -149,6 +149,7 @@ function saveSettings() {
notifTournaments: document.getElementById('set-notif-tournaments').checked, notifTournaments: document.getElementById('set-notif-tournaments').checked,
}; };
localStorage.setItem('el3ab_settings', JSON.stringify(settings)); localStorage.setItem('el3ab_settings', JSON.stringify(settings));
App.toggleSound(settings.sound);
} }
async function saveProfile() { async function saveProfile() {
......
...@@ -57,6 +57,17 @@ ...@@ -57,6 +57,17 @@
/* Transitions */ /* Transitions */
--ease: cubic-bezier(0.4, 0, 0.2, 1); --ease: cubic-bezier(0.4, 0, 0.2, 1);
/* Elevation system */
--elevation-1: 0 1px 2px rgba(0,0,0,0.2);
--elevation-2: 0 4px 12px rgba(0,0,0,0.3);
--elevation-3: 0 8px 24px rgba(0,0,0,0.4);
--elevation-4: 0 12px 40px rgba(0,0,0,0.5);
/* Responsive typography */
--text-page: clamp(22px, 2vw + 16px, 28px);
--text-section: clamp(12px, 0.5vw + 10px, 14px);
--text-body: clamp(13px, 0.5vw + 11px, 15px);
/* Board Theme */ /* Board Theme */
--board-light: #E8EDF9; --board-light: #E8EDF9;
--board-dark: #7195D1; --board-dark: #7195D1;
...@@ -442,7 +453,17 @@ img { ...@@ -442,7 +453,17 @@ img {
.btn:active { transform: scale(0.97); } .btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.5; pointer-events: none; } .btn:disabled { opacity: 0.5; pointer-events: none; }
.btn-gold { background: var(--gold); color: var(--text-inverse); } .btn-gold { background: var(--gold); color: var(--text-inverse); position: relative; overflow: hidden; }
.btn-gold::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 60%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
animation: shimmer-sweep 3s ease-in-out infinite;
}
.btn-cyan { background: var(--cyan); color: var(--text-inverse); } .btn-cyan { background: var(--cyan); color: var(--text-inverse); }
.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text-2); } .btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text-2); }
.btn-error { background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.2); color: var(--error); } .btn-error { background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.2); color: var(--error); }
...@@ -1044,6 +1065,293 @@ img { ...@@ -1044,6 +1065,293 @@ img {
} }
} }
/* ===== Win Streak Fire Badge ===== */
.streak-fire::before {
content: '🔥';
position: absolute;
top: -2px;
right: -2px;
font-size: 10px;
animation: fire-flicker 1s ease-in-out infinite alternate;
}
@keyframes fire-flicker {
from { transform: scale(1); }
to { transform: scale(1.2); }
}
/* ===== Bottom Sheet Modal ===== */
.sheet-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 90;
opacity: 0;
transition: opacity 0.3s var(--ease);
pointer-events: none;
}
.sheet-backdrop.active {
opacity: 1;
pointer-events: auto;
}
.sheet {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 91;
background: var(--bg-1);
border-top-left-radius: var(--radius-xl);
border-top-right-radius: var(--radius-xl);
padding: 16px 20px calc(20px + env(safe-area-inset-bottom));
transform: translateY(100%);
transition: transform 0.3s var(--ease);
max-height: 80vh;
overflow-y: auto;
}
.sheet.active {
transform: translateY(0);
}
.sheet-handle {
width: 36px;
height: 4px;
background: var(--text-3);
border-radius: var(--radius-full);
margin: 0 auto 16px;
}
/* ===== Pull to Refresh ===== */
.ptr-indicator {
position: fixed;
top: calc(var(--header-h) + 8px);
left: 50%;
transform: translateX(-50%) scale(0);
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--bg-2);
border: 2px solid var(--cyan);
display: flex;
align-items: center;
justify-content: center;
z-index: 35;
transition: transform 0.2s var(--ease);
}
.ptr-indicator.active {
transform: translateX(-50%) scale(1);
}
.ptr-indicator.loading {
transform: translateX(-50%) scale(1);
animation: ptr-spin 0.8s linear infinite;
}
@keyframes ptr-spin {
to { transform: translateX(-50%) scale(1) rotate(360deg); }
}
/* ===== Enhanced Toasts ===== */
.toast {
display: flex;
align-items: center;
gap: 10px;
position: relative;
overflow: hidden;
}
.toast-icon {
width: 18px;
height: 18px;
flex-shrink: 0;
}
.toast-progress {
position: absolute;
bottom: 0;
left: 0;
height: 2px;
background: var(--cyan);
animation: toast-countdown 3s linear forwards;
}
.toast-success .toast-progress { background: var(--success); }
.toast-error .toast-progress { background: var(--error); }
@keyframes toast-countdown {
from { width: 100%; }
to { width: 0%; }
}
/* ===== Nav Sliding Indicator ===== */
.nav-desktop-item.active {
position: relative;
}
.nav-desktop-item.active::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 24px;
background: var(--cyan);
border-radius: var(--radius-full);
}
.nav-bottom-item.active::after {
content: '';
position: absolute;
bottom: 8px;
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 3px;
background: var(--cyan);
border-radius: var(--radius-full);
}
.nav-bottom-item {
position: relative;
}
/* ===== Animated Background (Games Hub) ===== */
.bg-animated {
background: linear-gradient(135deg, var(--bg-0), var(--bg-1), var(--bg-2), var(--bg-1));
background-size: 400% 400%;
animation: bg-shift 15s ease infinite;
}
@keyframes bg-shift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
/* ===== Gold Shimmer ===== */
.shimmer {
position: relative;
overflow: hidden;
}
.shimmer::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 60%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.15), transparent);
animation: shimmer-sweep 3s ease-in-out infinite;
}
@keyframes shimmer-sweep {
0% { left: -100%; }
100% { left: 150%; }
}
/* ===== Celebration Overlay ===== */
.celebrate-overlay {
position: fixed;
inset: 0;
z-index: 200;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.7);
animation: celebrate-in 0.3s var(--ease);
}
@keyframes celebrate-in {
from { opacity: 0; }
to { opacity: 1; }
}
.celebrate-title {
font-size: 28px;
font-weight: 700;
color: var(--gold);
text-shadow: 0 0 20px rgba(231, 168, 50, 0.5);
animation: celebrate-bounce 0.5s var(--ease) 0.2s both;
}
@keyframes celebrate-bounce {
0% { transform: scale(0.5); opacity: 0; }
60% { transform: scale(1.1); }
100% { transform: scale(1); opacity: 1; }
}
.confetti-particle {
position: absolute;
width: 8px;
height: 8px;
border-radius: 2px;
animation: confetti-fall 2.5s ease-out forwards;
}
@keyframes confetti-fall {
0% {
transform: translateY(0) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(100vh) rotate(720deg);
opacity: 0;
}
}
/* ===== Responsive Typography ===== */
.page-title {
font-size: var(--text-page);
}
.section-title {
font-size: var(--text-section);
}
/* ===== Safe Area Handling ===== */
@supports (padding: env(safe-area-inset-top)) {
.main-inner {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
}
/* ===== Landscape Game Layout ===== */
@media (orientation: landscape) and (max-height: 500px) {
.header { height: 40px; }
.header-inner { padding: 0 12px; }
.nav-bottom { height: 48px; }
.main { padding-bottom: 48px; }
}
/* ===== Clock Urgency (Chess) ===== */
.clock-urgent {
animation: clock-pulse 1s ease-in-out infinite;
color: var(--error) !important;
}
.clock-critical {
animation: clock-shake 0.3s ease-in-out infinite;
color: var(--error) !important;
}
@keyframes clock-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
@keyframes clock-shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-2px); }
75% { transform: translateX(2px); }
}
/* Reduced motion */ /* Reduced motion */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
*, *::before, *::after { *, *::before, *::after {
......
...@@ -292,15 +292,16 @@ ...@@ -292,15 +292,16 @@
} }
.ludo-dice--rolling { .ludo-dice--rolling {
animation: ludo-dice-roll 0.4s ease; animation: ludo-dice-roll-3d 0.5s ease;
perspective: 300px;
} }
@keyframes ludo-dice-roll { @keyframes ludo-dice-roll-3d {
0% { transform: rotate(0deg) scale(1); } 0% { transform: rotateX(0deg) rotateY(0deg) scale(1); }
25% { transform: rotate(90deg) scale(0.9); } 25% { transform: rotateX(180deg) rotateY(90deg) scale(0.85); }
50% { transform: rotate(180deg) scale(1.1); } 50% { transform: rotateX(360deg) rotateY(180deg) scale(1.1); }
75% { transform: rotate(270deg) scale(0.9); } 75% { transform: rotateX(540deg) rotateY(270deg) scale(0.9); }
100% { transform: rotate(360deg) scale(1); } 100% { transform: rotateX(720deg) rotateY(360deg) scale(1); }
} }
.ludo-dice-dot { .ludo-dice-dot {
...@@ -614,3 +615,24 @@ ...@@ -614,3 +615,24 @@
60% { transform: translate(-50%, -50%) scale(0.9, 1.1); } 60% { transform: translate(-50%, -50%) scale(0.9, 1.1); }
100% { transform: translate(-50%, -50%) scale(1, 1); } 100% { transform: translate(-50%, -50%) scale(1, 1); }
} }
/* Capture explosion particles */
.ludo-capture-particle {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
pointer-events: none;
animation: capture-burst 0.6s ease-out forwards;
}
@keyframes capture-burst {
0% {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
}
100% {
transform: translate(var(--px, 0), var(--py, 0)) scale(0);
opacity: 0;
}
}
...@@ -81,6 +81,10 @@ ...@@ -81,6 +81,10 @@
<path d="M5 12h14m-7-7l7 7-7 7"/> <path d="M5 12h14m-7-7l7 7-7 7"/>
</symbol> </symbol>
<symbol id="icon-arrow-down" viewBox="0 0 24 24">
<path d="M12 5v14m-7-7l7 7 7-7"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24"> <symbol id="icon-search" viewBox="0 0 24 24">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/> <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</symbol> </symbol>
......
...@@ -48,8 +48,23 @@ const App = { ...@@ -48,8 +48,23 @@ const App = {
if (!container) return; if (!container) return;
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'toast' + (type !== 'info' ? ' toast-' + type : ''); el.className = 'toast' + (type !== 'info' ? ' toast-' + type : '');
el.textContent = message; const icons = { success: 'check', error: 'x', info: 'bell' };
const icon = icons[type] || icons.info;
el.innerHTML = '<svg class="toast-icon"><use href="/public/icons/sprite.svg#icon-' + icon + '"></use></svg>' +
'<span>' + message + '</span><span class="toast-progress"></span>';
container.appendChild(el); container.appendChild(el);
// Swipe to dismiss
let startX = 0;
el.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; });
el.addEventListener('touchmove', (e) => {
const diff = e.touches[0].clientX - startX;
if (Math.abs(diff) > 20) el.style.transform = 'translateX(' + diff + 'px)';
});
el.addEventListener('touchend', (e) => {
const diff = e.changedTouches[0].clientX - startX;
if (Math.abs(diff) > 80) { el.remove(); return; }
el.style.transform = '';
});
setTimeout(() => { setTimeout(() => {
el.style.opacity = '0'; el.style.opacity = '0';
el.style.transform = 'translateY(-8px)'; el.style.transform = 'translateY(-8px)';
...@@ -65,8 +80,13 @@ const App = { ...@@ -65,8 +80,13 @@ const App = {
const p = data.profile; const p = data.profile;
const coins = document.getElementById('header-coins'); const coins = document.getElementById('header-coins');
const gems = document.getElementById('header-gems'); const gems = document.getElementById('header-gems');
if (coins) coins.textContent = (p.coins || 0).toLocaleString(); if (coins) this.animateNumber(coins, p.coins || 0);
if (gems) gems.textContent = (p.gems || 0).toLocaleString(); if (gems) this.animateNumber(gems, p.gems || 0);
// Win streak fire badge
const streak = p.win_streak || 0;
document.querySelectorAll('.nav-desktop-item[href="/profile"], .nav-bottom-item[href="/profile"]').forEach(el => {
el.classList.toggle('streak-fire', streak >= 3);
});
} }
}, },
...@@ -98,6 +118,118 @@ const App = { ...@@ -98,6 +118,118 @@ const App = {
if (t < 1) requestAnimationFrame(step); if (t < 1) requestAnimationFrame(step);
}; };
requestAnimationFrame(step); requestAnimationFrame(step);
},
// ===== Bottom Sheet =====
showSheet(html) {
let backdrop = document.querySelector('.sheet-backdrop');
let sheet = document.querySelector('.sheet');
if (!backdrop) {
backdrop = document.createElement('div');
backdrop.className = 'sheet-backdrop';
document.body.appendChild(backdrop);
sheet = document.createElement('div');
sheet.className = 'sheet';
sheet.innerHTML = '<div class="sheet-handle"></div><div class="sheet-content"></div>';
document.body.appendChild(sheet);
backdrop.addEventListener('click', () => this.hideSheet());
}
sheet.querySelector('.sheet-content').innerHTML = html;
requestAnimationFrame(() => {
backdrop.classList.add('active');
sheet.classList.add('active');
});
},
hideSheet() {
const backdrop = document.querySelector('.sheet-backdrop');
const sheet = document.querySelector('.sheet');
if (backdrop) backdrop.classList.remove('active');
if (sheet) sheet.classList.remove('active');
},
// ===== Request Cache =====
_cache: {},
async cachedFetch(url, ttl) {
ttl = ttl || 30000;
const now = Date.now();
if (this._cache[url] && (now - this._cache[url].time) < ttl) {
return this._cache[url].data;
}
const data = await this.fetch(url);
if (data) this._cache[url] = { data, time: now };
return data;
},
invalidateCache(url) {
if (url) delete this._cache[url];
else this._cache = {};
},
// ===== Sound System =====
_soundEnabled: localStorage.getItem('el3ab_sound') !== 'off',
_audioCtx: null,
toggleSound(on) {
this._soundEnabled = on;
localStorage.setItem('el3ab_sound', on ? 'on' : 'off');
},
playSound(type) {
if (!this._soundEnabled) return;
if (!this._audioCtx) this._audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const ctx = this._audioCtx;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
gain.gain.value = 0.15;
const sounds = {
move: [440, 0.08, 'sine'],
capture: [520, 0.12, 'square'],
check: [660, 0.15, 'sawtooth'],
win: [523, 0.3, 'sine'],
lose: [220, 0.3, 'sine'],
click: [800, 0.05, 'sine'],
dice: [350, 0.1, 'triangle'],
notify: [600, 0.15, 'sine'],
};
const [freq, dur, wave] = sounds[type] || sounds.click;
osc.type = wave;
osc.frequency.value = freq;
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + dur);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + dur);
},
// ===== Celebration Overlay =====
celebrate(title) {
const overlay = document.createElement('div');
overlay.className = 'celebrate-overlay';
overlay.innerHTML = '<span class="celebrate-title">' + title + '</span>';
const colors = ['#E7A832', '#15D7FF', '#7C4DFF', '#34D399', '#EF4444', '#F59E0B'];
for (let i = 0; i < 30; i++) {
const p = document.createElement('span');
p.className = 'confetti-particle';
p.style.left = Math.random() * 100 + '%';
p.style.top = '-10px';
p.style.background = colors[Math.floor(Math.random() * colors.length)];
p.style.animationDelay = (Math.random() * 0.5) + 's';
p.style.animationDuration = (1.5 + Math.random()) + 's';
overlay.appendChild(p);
}
document.body.appendChild(overlay);
overlay.addEventListener('click', () => overlay.remove());
setTimeout(() => overlay.remove(), 3500);
},
// ===== Navigate (soft page transition) =====
navigate(url) {
const main = document.querySelector('.main-inner');
if (!main) { window.location.href = url; return; }
main.classList.add('page-exit');
setTimeout(() => { window.location.href = url; }, 200);
} }
}; };
...@@ -120,4 +252,46 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -120,4 +252,46 @@ document.addEventListener('DOMContentLoaded', () => {
btn.appendChild(ripple); btn.appendChild(ripple);
setTimeout(() => ripple.remove(), 500); setTimeout(() => ripple.remove(), 500);
}); });
// Pull-to-refresh (mobile)
let ptr_startY = 0, ptr_pulling = false;
const mainEl = document.querySelector('.main');
if (mainEl && 'ontouchstart' in window) {
const indicator = document.createElement('div');
indicator.className = 'ptr-indicator';
indicator.innerHTML = '<svg class="icon-sm" style="color:var(--cyan)"><use href="/public/icons/sprite.svg#icon-arrow-down"></use></svg>';
document.body.appendChild(indicator);
mainEl.addEventListener('touchstart', (e) => {
if (mainEl.scrollTop === 0) {
ptr_startY = e.touches[0].clientY;
ptr_pulling = true;
}
}, { passive: true });
mainEl.addEventListener('touchmove', (e) => {
if (!ptr_pulling) return;
const diff = e.touches[0].clientY - ptr_startY;
if (diff > 30) indicator.classList.add('active');
else indicator.classList.remove('active');
}, { passive: true });
mainEl.addEventListener('touchend', () => {
if (indicator.classList.contains('active')) {
indicator.classList.remove('active');
indicator.classList.add('loading');
setTimeout(() => {
indicator.classList.remove('loading');
window.location.reload();
}, 600);
}
ptr_pulling = false;
});
}
// Lazy image loader
document.querySelectorAll('.img-lazy img').forEach(img => {
if (img.complete) img.closest('.img-lazy').classList.add('img-lazy--loaded');
else img.addEventListener('load', () => img.closest('.img-lazy').classList.add('img-lazy--loaded'));
});
}); });
...@@ -399,8 +399,8 @@ const Game = { ...@@ -399,8 +399,8 @@ const Game = {
topClock.textContent = this.formatTime(topTime); topClock.textContent = this.formatTime(topTime);
bottomClock.textContent = this.formatTime(bottomTime); bottomClock.textContent = this.formatTime(bottomTime);
topClock.className = 'game-clock' + (this.activeClock === topColor ? ' active' : '') + (topTime <= 30 ? ' low' : ''); topClock.className = 'game-clock' + (this.activeClock === topColor ? ' active' : '') + (topTime <= 10 ? ' clock-critical' : topTime <= 30 ? ' clock-urgent' : '');
bottomClock.className = 'game-clock' + (this.activeClock === bottomColor ? ' active' : '') + (bottomTime <= 30 ? ' low' : ''); bottomClock.className = 'game-clock' + (this.activeClock === bottomColor ? ' active' : '') + (bottomTime <= 10 ? ' clock-critical' : bottomTime <= 30 ? ' clock-urgent' : '');
}, },
formatTime(seconds) { formatTime(seconds) {
......
...@@ -164,6 +164,7 @@ var LudoGame = (function() { ...@@ -164,6 +164,7 @@ var LudoGame = (function() {
var extraTurn = false; var extraTurn = false;
if (killed) { if (killed) {
UI.captureExplosion(killed.player, newPos);
UI.addLogEntry(getPlayerLabel(player) + ' اكل قطعة ' + getPlayerLabel(killed.player)); UI.addLogEntry(getPlayerLabel(player) + ' اكل قطعة ' + getPlayerLabel(killed.player));
extraTurn = true; extraTurn = true;
} }
......
...@@ -372,6 +372,29 @@ var LudoUI = (function() { ...@@ -372,6 +372,29 @@ var LudoUI = (function() {
if (logEl) logEl.innerHTML = ''; if (logEl) logEl.innerHTML = '';
} }
function captureExplosion(player, position) {
if (!piecesEl) return;
var coords = C.COORDINATES_MAP[position];
if (!coords) return;
var halfStep = C.STEP_LENGTH / 2;
var colors = { 0: '#E53935', 1: '#43A047', 2: '#FDD835', 3: '#1E88E5' };
var color = colors[player] || '#fff';
for (var i = 0; i < 10; i++) {
var p = document.createElement('span');
p.className = 'ludo-capture-particle';
p.style.top = (coords[1] * C.STEP_LENGTH + halfStep) + '%';
p.style.left = (coords[0] * C.STEP_LENGTH + halfStep) + '%';
p.style.background = color;
var angle = (Math.PI * 2 / 10) * i;
var dist = 20 + Math.random() * 15;
p.style.setProperty('--px', Math.cos(angle) * dist + 'px');
p.style.setProperty('--py', Math.sin(angle) * dist + 'px');
piecesEl.appendChild(p);
setTimeout(function(el) { el.remove(); }, 700, p);
}
if (typeof App !== 'undefined' && App.playSound) App.playSound('capture');
}
function setTurnElement(el) { turnEl = el; } function setTurnElement(el) { turnEl = el; }
function setLogElement(el) { logEl = el; } function setLogElement(el) { logEl = el; }
...@@ -396,6 +419,7 @@ var LudoUI = (function() { ...@@ -396,6 +419,7 @@ var LudoUI = (function() {
hideResult: hideResult, hideResult: hideResult,
addLogEntry: addLogEntry, addLogEntry: addLogEntry,
clearLog: clearLog, clearLog: clearLog,
captureExplosion: captureExplosion,
setTurnElement: setTurnElement, setTurnElement: setTurnElement,
setLogElement: setLogElement setLogElement: setLogElement
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment