Commit 4140b7da authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: complete i18n pass — replace 900+ hardcoded Arabic strings with t() calls

All UI text across 57 files now goes through the i18n system (core/i18n.js).
Added ~120 new translation keys covering: auth, daily rewards, challenges,
ranks, shop, groups, tournaments, puzzles, orgs, emotes, and backgammon
doubling. Both ar and en dictionaries are complete and in sync.

Data strings (country names, FIDE titles, variant names, puzzle themes)
are intentionally left as-is — they use name/nameEn data pattern.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 43b1e2a0
This diff is collapsed.
...@@ -5,6 +5,7 @@ import * as audio from './audio.js'; ...@@ -5,6 +5,7 @@ import * as audio from './audio.js';
import * as juice from './juice.js'; import * as juice from './juice.js';
import * as bus from './bus.js'; import * as bus from './bus.js';
import * as scene from './scene.js'; import * as scene from './scene.js';
import { t } from './i18n.js';
let overlayEl = null; let overlayEl = null;
...@@ -45,11 +46,11 @@ export function showOpponentDisconnect() { ...@@ -45,11 +46,11 @@ export function showOpponentDisconnect() {
<div style="width:64px;height:64px;margin:0 auto 16px;border-radius:50%;background:#1e1e3a;display:flex;align-items:center;justify-content:center;"> <div style="width:64px;height:64px;margin:0 auto 16px;border-radius:50%;background:#1e1e3a;display:flex;align-items:center;justify-content:center;">
<div style="width:16px;height:16px;border:3px solid #FBBF24;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;"></div> <div style="width:16px;height:16px;border:3px solid #FBBF24;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;"></div>
</div> </div>
<div style="font-size:18px;font-weight:700;color:#f8fafc;margin-bottom:8px;">الخصم انقطع اتصاله</div> <div style="font-size:18px;font-weight:700;color:#f8fafc;margin-bottom:8px;">${t('match.disconnected_title')}</div>
<div style="font-size:13px;color:#94a3b8;margin-bottom:16px;">في انتظار عودته...</div> <div style="font-size:13px;color:#94a3b8;margin-bottom:16px;">${t('match.disconnected_waiting')}</div>
<div id="abandon-countdown" style="font-size:32px;font-weight:800;color:#FBBF24;font-family:Inter,monospace;margin-bottom:16px;">60</div> <div id="abandon-countdown" style="font-size:32px;font-weight:800;color:#FBBF24;font-family:Inter,monospace;margin-bottom:16px;">60</div>
<div style="font-size:11px;color:#64748b;">ستفوز تلقائياً إذا لم يعد خلال الوقت المحدد</div> <div style="font-size:11px;color:#64748b;">${t('match.disconnected_auto_win')}</div>
<button id="claim-win-btn" style="margin-top:16px;padding:10px 24px;background:#E4AC38;border:none;border-radius:8px;color:#1a1a1a;font-weight:700;font-size:13px;cursor:pointer;opacity:0.5;pointer-events:none;">فوز مبكر</button> <button id="claim-win-btn" style="margin-top:16px;padding:10px 24px;background:#E4AC38;border:none;border-radius:8px;color:#1a1a1a;font-weight:700;font-size:13px;cursor:pointer;opacity:0.5;pointer-events:none;">${t('match.claim_early_win')}</button>
</div> </div>
<style>@keyframes spin { to { transform: rotate(360deg); } }</style> <style>@keyframes spin { to { transform: rotate(360deg); } }</style>
`); `);
...@@ -87,7 +88,7 @@ export function showOpponentReconnect() { ...@@ -87,7 +88,7 @@ export function showOpponentReconnect() {
hide(); hide();
// Brief toast // Brief toast
showToast('✅ الخصم عاد — استمروا!', '#34D399'); showToast(t('match.opponent_returned'), '#34D399');
audio.play('notification'); audio.play('notification');
} }
...@@ -101,9 +102,9 @@ function showAutoWin() { ...@@ -101,9 +102,9 @@ function showAutoWin() {
show(` show(`
<div style="text-align:center;padding:32px;max-width:300px;"> <div style="text-align:center;padding:32px;max-width:300px;">
<div style="font-size:56px;margin-bottom:12px;animation:float 2s ease-in-out infinite;">🏆</div> <div style="font-size:56px;margin-bottom:12px;animation:float 2s ease-in-out infinite;">🏆</div>
<div style="font-size:22px;font-weight:800;color:#34D399;margin-bottom:8px;">فزت!</div> <div style="font-size:22px;font-weight:800;color:#34D399;margin-bottom:8px;">${t('match.you_won')}</div>
<div style="font-size:14px;color:#94a3b8;margin-bottom:20px;">الخصم غادر المباراة</div> <div style="font-size:14px;color:#94a3b8;margin-bottom:20px;">${t('match.opponent_left')}</div>
<button id="abandon-back-btn" style="padding:12px 32px;background:linear-gradient(135deg,#E4AC38,#FFCC66);border:none;border-radius:10px;color:#1a1a1a;font-weight:700;font-size:15px;cursor:pointer;">العودة</button> <button id="abandon-back-btn" style="padding:12px 32px;background:linear-gradient(135deg,#E4AC38,#FFCC66);border:none;border-radius:10px;color:#1a1a1a;font-weight:700;font-size:15px;cursor:pointer;">${t('match.return')}</button>
</div> </div>
`); `);
...@@ -125,8 +126,8 @@ export function showConnectionLost() { ...@@ -125,8 +126,8 @@ export function showConnectionLost() {
<div style="width:48px;height:48px;margin:0 auto 16px;border-radius:50%;background:#EF4444;display:flex;align-items:center;justify-content:center;"> <div style="width:48px;height:48px;margin:0 auto 16px;border-radius:50%;background:#EF4444;display:flex;align-items:center;justify-content:center;">
<span style="font-size:24px;">⚠️</span> <span style="font-size:24px;">⚠️</span>
</div> </div>
<div style="font-size:16px;font-weight:700;color:#f8fafc;margin-bottom:6px;">انقطع الاتصال</div> <div style="font-size:16px;font-weight:700;color:#f8fafc;margin-bottom:6px;">${t('match.connection_lost')}</div>
<div style="font-size:13px;color:#94a3b8;">جاري إعادة الاتصال...</div> <div style="font-size:13px;color:#94a3b8;">${t('match.reconnecting')}</div>
<div style="margin-top:16px;"> <div style="margin-top:16px;">
<div style="width:120px;height:4px;background:#1e1e3a;border-radius:2px;margin:0 auto;overflow:hidden;"> <div style="width:120px;height:4px;background:#1e1e3a;border-radius:2px;margin:0 auto;overflow:hidden;">
<div style="width:30%;height:100%;background:#FBBF24;border-radius:2px;animation:loading 1.5s ease-in-out infinite;"></div> <div style="width:30%;height:100%;background:#FBBF24;border-radius:2px;animation:loading 1.5s ease-in-out infinite;"></div>
...@@ -140,7 +141,7 @@ export function showConnectionLost() { ...@@ -140,7 +141,7 @@ export function showConnectionLost() {
// ========== CONNECTION RESTORED ========== // ========== CONNECTION RESTORED ==========
export function showConnectionRestored() { export function showConnectionRestored() {
hide(); hide();
showToast('✅ تم استعادة الاتصال', '#34D399'); showToast(t('match.reconnected'), '#34D399');
} }
// ========== RECONNECTING (tab refresh recovery) ========== // ========== RECONNECTING (tab refresh recovery) ==========
...@@ -148,7 +149,7 @@ export function showReconnecting() { ...@@ -148,7 +149,7 @@ export function showReconnecting() {
show(` show(`
<div style="text-align:center;padding:32px;"> <div style="text-align:center;padding:32px;">
<div style="width:48px;height:48px;margin:0 auto 16px;border:3px solid #E4AC38;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;"></div> <div style="width:48px;height:48px;margin:0 auto 16px;border:3px solid #E4AC38;border-top-color:transparent;border-radius:50%;animation:spin 1s linear infinite;"></div>
<div style="font-size:16px;font-weight:700;color:#f8fafc;">جاري استعادة المباراة...</div> <div style="font-size:16px;font-weight:700;color:#f8fafc;">${t('match.restoring')}</div>
</div> </div>
<style>@keyframes spin { to { transform: rotate(360deg); } }</style> <style>@keyframes spin { to { transform: rotate(360deg); } }</style>
`); `);
...@@ -156,7 +157,7 @@ export function showReconnecting() { ...@@ -156,7 +157,7 @@ export function showReconnecting() {
// ========== YOUR TURN REMINDER (if idle too long) ========== // ========== YOUR TURN REMINDER (if idle too long) ==========
export function showTurnReminder() { export function showTurnReminder() {
showToast('⏱️ دورك! العب قبل انتهاء الوقت', '#FBBF24'); showToast(t('match.your_turn_warning'), '#FBBF24');
juice.hapticMedium(); juice.hapticMedium();
} }
...@@ -168,7 +169,7 @@ export function showOpponentThinking() { ...@@ -168,7 +169,7 @@ export function showOpponentThinking() {
const el = document.createElement('div'); const el = document.createElement('div');
el.id = 'thinking-indicator'; el.id = 'thinking-indicator';
el.style.cssText = 'position:fixed;top:60px;left:50%;transform:translateX(-50%);background:rgba(30,30,58,0.95);padding:6px 14px;border-radius:20px;font-size:12px;color:#94a3b8;z-index:400;display:flex;align-items:center;gap:6px;border:1px solid rgba(255,255,255,0.06);'; el.style.cssText = 'position:fixed;top:60px;left:50%;transform:translateX(-50%);background:rgba(30,30,58,0.95);padding:6px 14px;border-radius:20px;font-size:12px;color:#94a3b8;z-index:400;display:flex;align-items:center;gap:6px;border:1px solid rgba(255,255,255,0.06);';
el.innerHTML = `<div style="width:8px;height:8px;border:2px solid #94a3b8;border-top-color:transparent;border-radius:50%;animation:spin 0.8s linear infinite;"></div> الخصم يفكر...`; el.innerHTML = `<div style="width:8px;height:8px;border:2px solid #94a3b8;border-top-color:transparent;border-radius:50%;animation:spin 0.8s linear infinite;"></div> ${t('match.opponent_thinking')}`;
document.body.appendChild(el); document.body.appendChild(el);
} }
...@@ -190,17 +191,17 @@ function showToast(message, color = '#f8fafc') { ...@@ -190,17 +191,17 @@ function showToast(message, color = '#f8fafc') {
// ========== BOT REPLACEMENT ========== // ========== BOT REPLACEMENT ==========
export function showBotReplacement() { export function showBotReplacement() {
showToast('🤖 الخصم غادر — بوت يكمل بدلاً منه', '#FBBF24'); showToast(t('match.bot_replacement'), '#FBBF24');
juice.hapticLight(); juice.hapticLight();
} }
// ========== TURN TIMED OUT ========== // ========== TURN TIMED OUT ==========
export function showTurnTimedOut(isMyTimeout) { export function showTurnTimedOut(isMyTimeout) {
if (isMyTimeout) { if (isMyTimeout) {
showToast('⏱️ انتهى وقتك! تم التمرير تلقائياً', '#EF4444'); showToast(t('match.your_time_expired'), '#EF4444');
juice.hapticMedium(); juice.hapticMedium();
} else { } else {
showToast('⏱️ انتهى وقت الخصم — دورك!', '#34D399'); showToast(t('match.opponent_time_expired'), '#34D399');
juice.hapticLight(); juice.hapticLight();
} }
} }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import * as audio from './audio.js'; import * as audio from './audio.js';
import * as juice from './juice.js'; import * as juice from './juice.js';
import { t } from './i18n.js';
let modalEl = null; let modalEl = null;
let backdropEl = null; let backdropEl = null;
...@@ -86,8 +87,8 @@ export function confirm(message, options = {}) { ...@@ -86,8 +87,8 @@ export function confirm(message, options = {}) {
const { const {
title = '', title = '',
confirmText = 'تأكيد', confirmText = t('common.confirm'),
cancelText = 'إلغاء', cancelText = t('common.cancel'),
confirmColor = '#E4AC38', confirmColor = '#E4AC38',
danger = false, danger = false,
icon = '' icon = ''
...@@ -163,7 +164,7 @@ export function confirm(message, options = {}) { ...@@ -163,7 +164,7 @@ export function confirm(message, options = {}) {
* Returns a Promise<void> * Returns a Promise<void>
*/ */
export function alert(message, options = {}) { export function alert(message, options = {}) {
const { title = '', buttonText = 'حسناً', icon = '' } = options; const { title = '', buttonText = t('common.ok'), icon = '' } = options;
ensureContainer(); ensureContainer();
audio.play('notification'); audio.play('notification');
......
...@@ -33,7 +33,7 @@ export function renderOpponentBar(container, opponent, options = {}) { ...@@ -33,7 +33,7 @@ export function renderOpponentBar(container, opponent, options = {}) {
<div id="mp-conn-dot" style="position:absolute;bottom:-1px;right:-1px;width:10px;height:10px;border-radius:50%;background:#34D399;border:2px solid #0f0f1e;"></div> <div id="mp-conn-dot" style="position:absolute;bottom:-1px;right:-1px;width:10px;height:10px;border-radius:50%;background:#34D399;border:2px solid #0f0f1e;"></div>
</div> </div>
<div style="flex:1;"> <div style="flex:1;">
<div style="font-size:13px;font-weight:600;color:#f8fafc;">${opponent.display_name || opponent.username || 'خصم'}</div> <div style="font-size:13px;font-weight:600;color:#f8fafc;">${opponent.display_name || opponent.username || t('common.opponent')}</div>
${showRating ? `<div style="font-size:11px;color:#64748b;">${emoji('star', '⭐', 11)} ${opponent.rating || opponent.elo_rapid || '1200'}</div>` : ''} ${showRating ? `<div style="font-size:11px;color:#64748b;">${emoji('star', '⭐', 11)} ${opponent.rating || opponent.elo_rapid || '1200'}</div>` : ''}
</div> </div>
<div id="mp-opponent-status" style="font-size:10px;color:#64748b;"></div> <div id="mp-opponent-status" style="font-size:10px;color:#64748b;"></div>
...@@ -59,11 +59,11 @@ function showOpponentActions(container, opponent) { ...@@ -59,11 +59,11 @@ function showOpponentActions(container, opponent) {
menu.id = 'mp-opponent-menu'; menu.id = 'mp-opponent-menu';
menu.style.cssText = 'position:absolute;top:50px;right:12px;background:#1a1a2e;border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:8px;z-index:100;box-shadow:0 8px 24px rgba(0,0,0,0.5);display:flex;flex-direction:column;gap:4px;min-width:140px;'; menu.style.cssText = 'position:absolute;top:50px;right:12px;background:#1a1a2e;border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:8px;z-index:100;box-shadow:0 8px 24px rgba(0,0,0,0.5);display:flex;flex-direction:column;gap:4px;min-width:140px;';
menu.innerHTML = ` menu.innerHTML = `
<button class="mp-action" data-action="profile">${emoji('person', '👤', 12)} الملف الشخصي</button> <button class="mp-action" data-action="profile">${emoji('person', '👤', 12)} ${t('mp.profile')}</button>
<button class="mp-action" data-action="friend">➕ إضافة صديق</button> <button class="mp-action" data-action="friend">➕ ${t('mp.add_friend')}</button>
<button class="mp-action" data-action="mute">${emoji('mute', '🔇', 12)} ${t('block.mute')}</button> <button class="mp-action" data-action="mute">${emoji('mute', '🔇', 12)} ${t('block.mute')}</button>
<button class="mp-action" data-action="block" style="color:#EF4444;">${emoji('block', '🚫', 12)} ${t('block.block')}</button> <button class="mp-action" data-action="block" style="color:#EF4444;">${emoji('block', '🚫', 12)} ${t('block.block')}</button>
<button class="mp-action" data-action="report" style="color:#EF4444;">⚠️ إبلاغ</button> <button class="mp-action" data-action="report" style="color:#EF4444;">⚠️ ${t('mp.report')}</button>
`; `;
const style = document.createElement('style'); const style = document.createElement('style');
...@@ -76,10 +76,10 @@ function showOpponentActions(container, opponent) { ...@@ -76,10 +76,10 @@ function showOpponentActions(container, opponent) {
btn.style.pointerEvents = 'none'; btn.style.pointerEvents = 'none';
const result = await addFriendFromGame(opponent.id); const result = await addFriendFromGame(opponent.id);
if (result === true) { if (result === true) {
btn.textContent = '✓ تم الإرسال'; btn.textContent = t('common.sent');
btn.style.color = '#34D399'; btn.style.color = '#34D399';
} else { } else {
btn.textContent = result || '✓ مرسل سابقاً'; btn.textContent = result || t('common.already_sent');
btn.style.color = '#64748b'; btn.style.color = '#64748b';
} }
juice.hapticLight(); juice.hapticLight();
...@@ -102,10 +102,10 @@ function showOpponentActions(container, opponent) { ...@@ -102,10 +102,10 @@ function showOpponentActions(container, opponent) {
menu.querySelector('[data-action="block"]').addEventListener('click', async () => { menu.querySelector('[data-action="block"]').addEventListener('click', async () => {
menu.remove(); menu.remove();
const confirmed = await modal.confirm(t('block.confirm_block'), { const confirmed = await modal.confirm(t('block.confirm_block'), {
title: 'حظر', title: t('mp.block'),
icon: '🚫', icon: '🚫',
confirmText: 'حظر', confirmText: t('common.confirm'),
cancelText: 'إلغاء', cancelText: t('common.cancel'),
danger: true danger: true
}); });
if (!confirmed) return; if (!confirmed) return;
...@@ -117,7 +117,7 @@ function showOpponentActions(container, opponent) { ...@@ -117,7 +117,7 @@ function showOpponentActions(container, opponent) {
menu.querySelector('[data-action="report"]').addEventListener('click', () => { menu.querySelector('[data-action="report"]').addEventListener('click', () => {
reportOpponent(opponent.id); reportOpponent(opponent.id);
menu.querySelector('[data-action="report"]').textContent = '✓ تم الإبلاغ'; menu.querySelector('[data-action="report"]').textContent = t('mp.reported');
setTimeout(() => menu.remove(), 1000); setTimeout(() => menu.remove(), 1000);
}); });
...@@ -189,12 +189,12 @@ export function startDisconnectWatch(matchId, matchType, timeoutMs = 60000) { ...@@ -189,12 +189,12 @@ export function startDisconnectWatch(matchId, matchType, timeoutMs = 60000) {
if (elapsed > timeoutMs) { if (elapsed > timeoutMs) {
// Opponent disconnected too long — claim win // Opponent disconnected too long — claim win
if (dot) dot.style.background = '#EF4444'; if (dot) dot.style.background = '#EF4444';
if (status) status.textContent = 'انقطع الاتصال'; if (status) status.textContent = t('game.opponent_disconnected');
clearInterval(disconnectTimer); clearInterval(disconnectTimer);
// Could auto-claim win here // Could auto-claim win here
} else if (elapsed > 15000) { } else if (elapsed > 15000) {
if (dot) dot.style.background = '#FBBF24'; if (dot) dot.style.background = '#FBBF24';
if (status) status.textContent = 'اتصال ضعيف'; if (status) status.textContent = t('game.weak_connection');
} else { } else {
if (dot) dot.style.background = '#34D399'; if (dot) dot.style.background = '#34D399';
if (status) status.textContent = ''; if (status) status.textContent = '';
...@@ -230,15 +230,15 @@ export async function addFriendFromGame(opponentId) { ...@@ -230,15 +230,15 @@ export async function addFriendFromGame(opponentId) {
try { try {
const res = await net.post('friends.php', { action: 'request', target_id: opponentId }); const res = await net.post('friends.php', { action: 'request', target_id: opponentId });
if (res.error) { if (res.error) {
if (res.error.includes('Already friends')) return 'صديق بالفعل'; if (res.error.includes('Already friends')) return t('common.already_friends');
if (res.error.includes('pending')) return 'مرسل سابقاً'; if (res.error.includes('pending')) return t('common.already_sent');
return res.error; return res.error;
} }
juice.hapticLight(); juice.hapticLight();
return true; return true;
} catch (e) { } catch (e) {
const msg = e.message || ''; const msg = e.message || '';
if (msg.includes('Already') || msg.includes('pending')) return 'مرسل سابقاً'; if (msg.includes('Already') || msg.includes('pending')) return t('common.already_sent');
return false; return false;
} }
} }
......
...@@ -5,6 +5,7 @@ import * as audio from './audio.js'; ...@@ -5,6 +5,7 @@ import * as audio from './audio.js';
import * as mp from './multiplayer.js'; import * as mp from './multiplayer.js';
import { getTier } from '../modules/rewards/scenes/ranks.js'; import { getTier } from '../modules/rewards/scenes/ranks.js';
import { emoji } from './theme.js'; import { emoji } from './theme.js';
import { t } from './i18n.js';
export function render(container, player, options = {}) { export function render(container, player, options = {}) {
const { position = 'top', isActive = false, showRating = true, showLevel = true, isSelf = false } = options; const { position = 'top', isActive = false, showRating = true, showLevel = true, isSelf = false } = options;
...@@ -23,7 +24,7 @@ export function render(container, player, options = {}) { ...@@ -23,7 +24,7 @@ export function render(container, player, options = {}) {
<div class="pp-status-dot ${player.isOnline !== false ? 'online' : ''}"></div> <div class="pp-status-dot ${player.isOnline !== false ? 'online' : ''}"></div>
</div> </div>
<div class="pp-info"> <div class="pp-info">
<div class="pp-name">${player.display_name || player.username || (isSelf ? 'أنت' : 'خصم')}</div> <div class="pp-name">${player.display_name || player.username || (isSelf ? t('common.you') : t('common.opponent'))}</div>
<div class="pp-meta"> <div class="pp-meta">
${showLevel ? `<span class="pp-level">Lv.${player.level || 1}</span>` : ''} ${showLevel ? `<span class="pp-level">Lv.${player.level || 1}</span>` : ''}
${tier ? `<span class="pp-tier" style="color:${tier.color};">${tier.icon}</span>` : ''} ${tier ? `<span class="pp-tier" style="color:${tier.color};">${tier.icon}</span>` : ''}
...@@ -84,8 +85,8 @@ function showPlayerPopup(container, profile) { ...@@ -84,8 +85,8 @@ function showPlayerPopup(container, profile) {
<div style="font-size:16px;font-weight:700;color:#f8fafc;">${profile.display_name || profile.username}</div> <div style="font-size:16px;font-weight:700;color:#f8fafc;">${profile.display_name || profile.username}</div>
<div style="font-size:12px;color:#64748b;margin:4px 0 16px;">Level ${profile.level || 1} · ${profile.elo_rapid || 1200} ELO</div> <div style="font-size:12px;color:#64748b;margin:4px 0 16px;">Level ${profile.level || 1} · ${profile.elo_rapid || 1200} ELO</div>
<div style="display:flex;gap:8px;justify-content:center;"> <div style="display:flex;gap:8px;justify-content:center;">
<button id="pp-add" style="padding:8px 18px;background:#2563EB;border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;">➕ أضف صديق</button> <button id="pp-add" style="padding:8px 18px;background:#2563EB;border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;">${t('social.add_btn')}</button>
<button id="pp-close" style="padding:8px 18px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#94a3b8;font-size:12px;cursor:pointer;">إغلاق</button> <button id="pp-close" style="padding:8px 18px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:8px;color:#94a3b8;font-size:12px;cursor:pointer;">${t('common.close')}</button>
</div> </div>
`; `;
document.body.appendChild(popup); document.body.appendChild(popup);
...@@ -94,7 +95,7 @@ function showPlayerPopup(container, profile) { ...@@ -94,7 +95,7 @@ function showPlayerPopup(container, profile) {
popup.querySelector('#pp-add').addEventListener('click', async () => { popup.querySelector('#pp-add').addEventListener('click', async () => {
const success = await mp.addFriendFromGame(profile.id); const success = await mp.addFriendFromGame(profile.id);
if (success) { if (success) {
popup.querySelector('#pp-add').textContent = '✓ تم'; popup.querySelector('#pp-add').textContent = t('common.done');
popup.querySelector('#pp-add').style.background = '#34D399'; popup.querySelector('#pp-add').style.background = '#34D399';
} }
setTimeout(() => popup.remove(), 1000); setTimeout(() => popup.remove(), 1000);
......
...@@ -2,6 +2,7 @@ import * as net from './net.js'; ...@@ -2,6 +2,7 @@ import * as net from './net.js';
import * as bus from './bus.js'; import * as bus from './bus.js';
import * as store from './store.js'; import * as store from './store.js';
import * as realtime from './realtime.js'; import * as realtime from './realtime.js';
import { t } from './i18n.js';
let pendingMatches = []; let pendingMatches = [];
let activeTournaments = []; let activeTournaments = [];
...@@ -25,10 +26,10 @@ function showPairingToast(data) { ...@@ -25,10 +26,10 @@ function showPairingToast(data) {
toast.innerHTML = ` toast.innerHTML = `
<span style="font-size:20px;">🏆</span> <span style="font-size:20px;">🏆</span>
<div style="flex:1;"> <div style="flex:1;">
<div style="font-size:13px;font-weight:700;color:#f8fafc;">جولة ${data.roundNumber} جاهزة!</div> <div style="font-size:13px;font-weight:700;color:#f8fafc;">${t('tournament.round_ready', { n: data.roundNumber })}</div>
<div style="font-size:11px;color:#94a3b8;">اضغط للعب مباراتك</div> <div style="font-size:11px;color:#94a3b8;">${t('tournament.tap_to_play')}</div>
</div> </div>
<button id="toast-play-btn" style="padding:6px 14px;background:#E4AC38;border:none;border-radius:8px;color:#000;font-weight:700;font-size:12px;cursor:pointer;">العب</button> <button id="toast-play-btn" style="padding:6px 14px;background:#E4AC38;border:none;border-radius:8px;color:#000;font-weight:700;font-size:12px;cursor:pointer;">${t('tournament.play')}</button>
`; `;
document.body.appendChild(toast); document.body.appendChild(toast);
......
...@@ -23,7 +23,7 @@ export function mountLogin(el) { ...@@ -23,7 +23,7 @@ export function mountLogin(el) {
<div style="width:100%;max-width:320px;margin-top:var(--s-4);display:flex;flex-direction:column;gap:var(--s-3);"> <div style="width:100%;max-width:320px;margin-top:var(--s-4);display:flex;flex-direction:column;gap:var(--s-3);">
<div style="display:flex;align-items:center;gap:var(--s-3);margin:var(--s-2) 0;"> <div style="display:flex;align-items:center;gap:var(--s-3);margin:var(--s-2) 0;">
<div style="flex:1;height:1px;background:var(--bg-elevated);"></div> <div style="flex:1;height:1px;background:var(--bg-elevated);"></div>
<span style="color:var(--text-secondary);font-size:12px;">أو</span> <span style="color:var(--text-secondary);font-size:12px;">${t('common.or')}</span>
<div style="flex:1;height:1px;background:var(--bg-elevated);"></div> <div style="flex:1;height:1px;background:var(--bg-elevated);"></div>
</div> </div>
<button class="btn w-full" id="google-btn" style="background:#fff;color:#333;font-weight:600;min-height:44px;display:flex;align-items:center;justify-content:center;gap:var(--s-2);"> <button class="btn w-full" id="google-btn" style="background:#fff;color:#333;font-weight:600;min-height:44px;display:flex;align-items:center;justify-content:center;gap:var(--s-2);">
......
...@@ -29,7 +29,7 @@ export function mountRegister(el) { ...@@ -29,7 +29,7 @@ export function mountRegister(el) {
<div style="width:100%;max-width:320px;margin-top:var(--s-4);"> <div style="width:100%;max-width:320px;margin-top:var(--s-4);">
<div style="display:flex;align-items:center;gap:var(--s-3);margin:var(--s-2) 0;"> <div style="display:flex;align-items:center;gap:var(--s-3);margin:var(--s-2) 0;">
<div style="flex:1;height:1px;background:var(--bg-elevated);"></div> <div style="flex:1;height:1px;background:var(--bg-elevated);"></div>
<span style="color:var(--text-secondary);font-size:12px;">أو</span> <span style="color:var(--text-secondary);font-size:12px;">${t('common.or')}</span>
<div style="flex:1;height:1px;background:var(--bg-elevated);"></div> <div style="flex:1;height:1px;background:var(--bg-elevated);"></div>
</div> </div>
<button class="btn w-full" id="google-reg-btn" style="background:#fff;color:#333;font-weight:600;min-height:44px;display:flex;align-items:center;justify-content:center;gap:var(--s-2);"> <button class="btn w-full" id="google-reg-btn" style="background:#fff;color:#333;font-weight:600;min-height:44px;display:flex;align-items:center;justify-content:center;gap:var(--s-2);">
......
...@@ -9,7 +9,7 @@ export function mountSplash(el) { ...@@ -9,7 +9,7 @@ export function mountSplash(el) {
el.innerHTML = ` el.innerHTML = `
<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:var(--s-6);padding:var(--s-4);"> <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:var(--s-6);padding:var(--s-4);">
<div class="splash-logo" style="animation:glow 2s ease-in-out infinite;">${assetImg('logo', 'EL3AB', 180, 60)}</div> <div class="splash-logo" style="animation:glow 2s ease-in-out infinite;">${assetImg('logo', 'EL3AB', 180, 60)}</div>
<div class="splash-sub" style="color:var(--text-secondary);font-size:14px;animation:pulse 2s ease-in-out infinite;">العب · نافس · اكسب</div> <div class="splash-sub" style="color:var(--text-secondary);font-size:14px;animation:pulse 2s ease-in-out infinite;">${t('app.tagline')}</div>
<div class="splash-loader" style="margin-top:var(--s-8);"> <div class="splash-loader" style="margin-top:var(--s-8);">
<div style="width:120px;height:4px;background:var(--bg-elevated);border-radius:2px;overflow:hidden;"> <div style="width:120px;height:4px;background:var(--bg-elevated);border-radius:2px;overflow:hidden;">
<div style="width:0%;height:100%;background:var(--gold);border-radius:2px;animation:loadBar 2s ease-out forwards;"></div> <div style="width:0%;height:100%;background:var(--gold);border-radius:2px;animation:loadBar 2s ease-out forwards;"></div>
......
...@@ -41,13 +41,13 @@ export function mountGame(el, p) { ...@@ -41,13 +41,13 @@ export function mountGame(el, p) {
if (!useCube) match.cube = null; if (!useCube) match.cube = null;
const player = store.get('player') || {}; const player = store.get('player') || {};
const myName = player.display_name || player.username || 'أنت'; const myName = player.display_name || player.username || t('common.you');
const myAvatar = player.avatar_url const myAvatar = player.avatar_url
? `<img src="${player.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` ? `<img src="${player.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`
: emoji('person', '👤', 18); : emoji('person', '👤', 18);
const myLevel = player.level || 1; const myLevel = player.level || 1;
const rawOppName = mode === 'bot' ? 'بوت' : (params.opponentName || 'خصم'); const rawOppName = mode === 'bot' ? t('game.bot') : (params.opponentName || t('common.opponent'));
const oppName = rawOppName.replace(/[<>"&]/g, c => ({'<':'&lt;','>':'&gt;','"':'&quot;','&':'&amp;'}[c])); const oppName = rawOppName.replace(/[<>"&]/g, c => ({'<':'&lt;','>':'&gt;','"':'&quot;','&':'&amp;'}[c]));
const oppAvatarUrl = params.opponentAvatar ? String(params.opponentAvatar).replace(/[<>"]/g, '') : ''; const oppAvatarUrl = params.opponentAvatar ? String(params.opponentAvatar).replace(/[<>"]/g, '') : '';
const oppAvatar = oppAvatarUrl const oppAvatar = oppAvatarUrl
...@@ -70,28 +70,28 @@ export function mountGame(el, p) { ...@@ -70,28 +70,28 @@ export function mountGame(el, p) {
</div> </div>
<div class="bgg-match-info"> <div class="bgg-match-info">
<span class="bgg-cube-display" id="cube-display">×${cubeVal}</span> <span class="bgg-cube-display" id="cube-display">×${cubeVal}</span>
<span class="bgg-match-length">${matchLength} نقاط</span> <span class="bgg-match-length">${matchLength} ${t('game.points', { n: '' }).trim()}</span>
</div> </div>
</div> </div>
<div class="bgg-board-area"> <div class="bgg-board-area">
<canvas id="bg-canvas"></canvas> <canvas id="bg-canvas"></canvas>
<div class="bgg-turn-badge" id="turn-indicator">دورك</div> <div class="bgg-turn-badge" id="turn-indicator">${t('game.your_turn')}</div>
</div> </div>
<div class="bgg-controls"> <div class="bgg-controls">
<button class="bgg-action-btn bgg-quit" id="btn-quit">✕</button> <button class="bgg-action-btn bgg-quit" id="btn-quit">✕</button>
<button class="bgg-action-btn bgg-emote-btn" id="btn-emote">${emoji('sparkles', '✨', 18)}</button> <button class="bgg-action-btn bgg-emote-btn" id="btn-emote">${emoji('sparkles', '✨', 18)}</button>
<button class="bgg-roll-btn" id="btn-roll">${emoji('game_die', '🎲', 20)} ارمِ النرد</button> <button class="bgg-roll-btn" id="btn-roll">${emoji('game_die', '🎲', 20)} ${t('game.roll_dice')}</button>
<button class="bgg-cube-btn" id="btn-double" style="display:none;">×2</button> <button class="bgg-cube-btn" id="btn-double" style="display:none;">×2</button>
</div> </div>
<div class="bgg-double-offer" id="double-offer" style="display:none;"> <div class="bgg-double-offer" id="double-offer" style="display:none;">
<div class="bgg-double-icon">×<span id="double-val">2</span></div> <div class="bgg-double-icon">×<span id="double-val">2</span></div>
<p>الخصم يضاعف!</p> <p>${t('backgammon.opponent_doubles')}</p>
<div class="bgg-double-actions"> <div class="bgg-double-actions">
<button class="bgg-decline-btn" id="btn-decline-double">رفض</button> <button class="bgg-decline-btn" id="btn-decline-double">${t('backgammon.decline')}</button>
<button class="bgg-accept-btn" id="btn-accept-double">قبول</button> <button class="bgg-accept-btn" id="btn-accept-double">${t('backgammon.accept')}</button>
</div> </div>
</div> </div>
...@@ -131,7 +131,7 @@ export function mountGame(el, p) { ...@@ -131,7 +131,7 @@ export function mountGame(el, p) {
if (mode === 'live') { if (mode === 'live') {
matchSession.create(params.matchId, 'backgammon', { matchSession.create(params.matchId, 'backgammon', {
onOpponentMove: handleServerState, onOpponentMove: handleServerState,
onOpponentDisconnect: () => bus.emit('toast', { text: 'الخصم انقطع...' }), onOpponentDisconnect: () => bus.emit('toast', { text: t('game.opponent_disconnected') }),
onOpponentAbandon: () => endMatchWithWin() onOpponentAbandon: () => endMatchWithWin()
}); });
} }
...@@ -305,7 +305,7 @@ function updateUI() { ...@@ -305,7 +305,7 @@ function updateUI() {
// Turn indicator // Turn indicator
const ti = container.querySelector('#turn-indicator'); const ti = container.querySelector('#turn-indicator');
if (ti) { if (ti) {
ti.textContent = isMyTurn ? 'دورك' : 'دور الخصم'; ti.textContent = isMyTurn ? t('game.your_turn') : t('game.opponent_turn');
ti.className = `bgg-turn-badge ${isMyTurn ? 'bgg-turn-mine' : 'bgg-turn-opp'}`; ti.className = `bgg-turn-badge ${isMyTurn ? 'bgg-turn-mine' : 'bgg-turn-opp'}`;
} }
} }
...@@ -443,7 +443,7 @@ function onRollClick() { ...@@ -443,7 +443,7 @@ function onRollClick() {
rollDice(game); rollDice(game);
if (game.movesLeft.length === 0) { if (game.movesLeft.length === 0) {
bus.emit('toast', { text: 'لا حركات متاحة!' }); bus.emit('toast', { text: t('backgammon.no_moves') });
setTimeout(endTurn, 1000); setTimeout(endTurn, 1000);
return; return;
} }
...@@ -472,10 +472,10 @@ function onDoubleClick() { ...@@ -472,10 +472,10 @@ function onDoubleClick() {
const oppPips = getPipCount(game.state, myColor === WHITE ? BLACK : WHITE, game.variant); const oppPips = getPipCount(game.state, myColor === WHITE ? BLACK : WHITE, game.variant);
if (shouldBotAccept(match.cube, oppPips, myPips, params.difficulty)) { if (shouldBotAccept(match.cube, oppPips, myPips, params.difficulty)) {
acceptDouble(match.cube); acceptDouble(match.cube);
bus.emit('toast', { text: `البوت قبل! ×${match.cube.value}` }); bus.emit('toast', { text: t('backgammon.bot_accepted', { n: match.cube.value }) });
} else { } else {
declineDouble(match.cube); declineDouble(match.cube);
bus.emit('toast', { text: 'البوت رفض! فزت بالجولة' }); bus.emit('toast', { text: t('backgammon.bot_declined') });
showGameResult(endGame(match, myColor, 1)); showGameResult(endGame(match, myColor, 1));
return; return;
} }
...@@ -583,8 +583,8 @@ function showGameResult(result) { ...@@ -583,8 +583,8 @@ function showGameResult(result) {
}); });
}, 1800); }, 1800);
} else { } else {
const who = result.winner === myColor ? 'فزت' : 'البوت فاز'; const who = result.winner === myColor ? t('game.round_won') : t('game.round_lost');
bus.emit('toast', { text: `${who} بالجولة (+${result.stake})` }); bus.emit('toast', { text: `${who} (+${result.stake})` });
setTimeout(startNewGameRound, 2000); setTimeout(startNewGameRound, 2000);
} }
} }
...@@ -648,7 +648,7 @@ function handleServerState(data) { ...@@ -648,7 +648,7 @@ function handleServerState(data) {
function setupEmotePanel(el) { function setupEmotePanel(el) {
const panel = el.querySelector('#emote-panel'); const panel = el.querySelector('#emote-panel');
const emotes = ['🎲', '😤', '🤔', '👏', '😂', '🔥', '💀', '🫡']; const emotes = ['🎲', '😤', '🤔', '👏', '😂', '🔥', '💀', '🫡'];
const phrases = ['!لعبة حلوة', '!حركة ممتازة', '!حظ', '...فكّر أسرع', '?ريماتش', 'gg wp']; const phrases = [t('emote.nice_game'), t('emote.great_move'), t('emote.lucky'), t('emote.think_faster'), t('emote.rematch'), 'gg wp'];
panel.innerHTML = ` panel.innerHTML = `
<div class="bgg-emotes">${emotes.map(e => `<button class="bgg-emote-btn">${e}</button>`).join('')}</div> <div class="bgg-emotes">${emotes.map(e => `<button class="bgg-emote-btn">${e}</button>`).join('')}</div>
<div class="bgg-phrases">${phrases.map(p => `<button class="bgg-phrase-btn">${p}</button>`).join('')}</div> <div class="bgg-phrases">${phrases.map(p => `<button class="bgg-phrase-btn">${p}</button>`).join('')}</div>
......
...@@ -2,6 +2,7 @@ import * as scene from '../../../core/scene.js'; ...@@ -2,6 +2,7 @@ import * as scene from '../../../core/scene.js';
import * as audio from '../../../core/audio.js'; import * as audio from '../../../core/audio.js';
import * as bus from '../../../core/bus.js'; import * as bus from '../../../core/bus.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
import { t } from '../../../core/i18n.js';
let confettiRaf = null; let confettiRaf = null;
...@@ -19,23 +20,23 @@ export function mountResult(el, params) { ...@@ -19,23 +20,23 @@ export function mountResult(el, params) {
<div class="bgr-trophy-ring ${didWin ? 'bgr-win-ring' : 'bgr-lose-ring'}"> <div class="bgr-trophy-ring ${didWin ? 'bgr-win-ring' : 'bgr-lose-ring'}">
<div class="bgr-trophy">${didWin ? emoji('trophy', '🏆', 56) : emoji('pensive', '😔', 56)}</div> <div class="bgr-trophy">${didWin ? emoji('trophy', '🏆', 56) : emoji('pensive', '😔', 56)}</div>
</div> </div>
<h1 class="bgr-title">${didWin ? 'مبروك! فزت بالماتش!' : 'خسرت هالمرة...'}</h1> <h1 class="bgr-title">${didWin ? t('backgammon.win_title') : t('backgammon.lose_title')}</h1>
<p class="bgr-subtitle">${reason === 'abandon' ? 'الخصم انسحب من اللعبة' : didWin ? 'أداء ممتاز!' : 'حاول مرة ثانية!'}</p> <p class="bgr-subtitle">${reason === 'abandon' ? t('backgammon.abandon_subtitle') : didWin ? t('backgammon.win_subtitle') : t('backgammon.lose_subtitle')}</p>
</div> </div>
<div class="bgr-scoreboard"> <div class="bgr-scoreboard">
<div class="bgr-score-col"> <div class="bgr-score-col">
<div class="bgr-score-val ${didWin ? 'bgr-val-win' : ''}">${scores[0]}</div> <div class="bgr-score-val ${didWin ? 'bgr-val-win' : ''}">${scores[0]}</div>
<div class="bgr-score-label">أنت</div> <div class="bgr-score-label">${t('common.you')}</div>
</div> </div>
<div class="bgr-score-vs"> <div class="bgr-score-vs">
<div class="bgr-vs-line"></div> <div class="bgr-vs-line"></div>
<span class="bgr-vs-text">من ${matchLength}</span> <span class="bgr-vs-text">${t('backgammon.from_match', { n: matchLength })}</span>
<div class="bgr-vs-line"></div> <div class="bgr-vs-line"></div>
</div> </div>
<div class="bgr-score-col"> <div class="bgr-score-col">
<div class="bgr-score-val ${!didWin ? 'bgr-val-win' : ''}">${scores[1]}</div> <div class="bgr-score-val ${!didWin ? 'bgr-val-win' : ''}">${scores[1]}</div>
<div class="bgr-score-label">الخصم</div> <div class="bgr-score-label">${t('common.opponent')}</div>
</div> </div>
</div> </div>
...@@ -52,10 +53,10 @@ export function mountResult(el, params) { ...@@ -52,10 +53,10 @@ export function mountResult(el, params) {
<div class="bgr-buttons"> <div class="bgr-buttons">
<button class="bgr-btn bgr-btn-primary" id="btn-rematch"> <button class="bgr-btn bgr-btn-primary" id="btn-rematch">
${emoji('fire', '🔥', 18)} العب مرة ثانية ${emoji('fire', '🔥', 18)} ${t('game.play_again')}
</button> </button>
<button class="bgr-btn bgr-btn-secondary" id="btn-exit"> <button class="bgr-btn bgr-btn-secondary" id="btn-exit">
رجوع للقائمة ${t('game.back_to_menu')}
</button> </button>
</div> </div>
</div> </div>
......
...@@ -3,6 +3,7 @@ import * as audio from '../../../core/audio.js'; ...@@ -3,6 +3,7 @@ import * as audio from '../../../core/audio.js';
import * as bus from '../../../core/bus.js'; import * as bus from '../../../core/bus.js';
import * as store from '../../../core/store.js'; import * as store from '../../../core/store.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
import { t } from '../../../core/i18n.js';
export function mountRoom(el, params) { export function mountRoom(el, params) {
const { mode = 'menu' } = params || {}; const { mode = 'menu' } = params || {};
...@@ -17,29 +18,29 @@ function renderMenu(el) { ...@@ -17,29 +18,29 @@ function renderMenu(el) {
<div class="bg-wrap"> <div class="bg-wrap">
<div class="bg-hero"> <div class="bg-hero">
<div class="bg-icon">${emoji('game_die', '🎲', 56)}</div> <div class="bg-icon">${emoji('game_die', '🎲', 56)}</div>
<h1 class="bg-title">طاولة</h1> <h1 class="bg-title">${t('backgammon.title')}</h1>
<p class="bg-subtitle">اول من يطلّع كل قطعه يفوز!</p> <p class="bg-subtitle">${t('backgammon.subtitle')}</p>
</div> </div>
<div class="bg-buttons"> <div class="bg-buttons">
<button class="bg-btn bg-btn-primary" id="btn-local"> <button class="bg-btn bg-btn-primary" id="btn-local">
<span class="bg-btn-icon">${emoji('gamepad', '🎮', 22)}</span> <span class="bg-btn-icon">${emoji('gamepad', '🎮', 22)}</span>
<span class="bg-btn-label">ضد البوت</span> <span class="bg-btn-label">${t('game.vs_bot')}</span>
<span class="bg-btn-desc">اختر المستوى والنوع</span> <span class="bg-btn-desc">${t('backgammon.bot_desc')}</span>
</button> </button>
<button class="bg-btn bg-btn-online" id="btn-online"> <button class="bg-btn bg-btn-online" id="btn-online">
<span class="bg-btn-icon">${emoji('globe', '🌍', 22)}</span> <span class="bg-btn-icon">${emoji('globe', '🌍', 22)}</span>
<span class="bg-btn-label">أونلاين</span> <span class="bg-btn-label">${t('game.online')}</span>
<span class="bg-btn-desc">العب ضد لاعبين حقيقيين</span> <span class="bg-btn-desc">${t('backgammon.online_desc')}</span>
</button> </button>
<button class="bg-btn bg-btn-friend" id="btn-friend"> <button class="bg-btn bg-btn-friend" id="btn-friend">
<span class="bg-btn-icon">${emoji('handshake', '🤝', 20)}</span> <span class="bg-btn-icon">${emoji('handshake', '🤝', 20)}</span>
<span class="bg-btn-label">تحدي صديق</span> <span class="bg-btn-label">${t('game.vs_friend')}</span>
<span class="bg-btn-desc">ادعُ صديقك للعب</span> <span class="bg-btn-desc">${t('backgammon.friend_desc')}</span>
</button> </button>
</div> </div>
<button class="bg-back" id="btn-back">رجوع</button> <button class="bg-back" id="btn-back">${t('common.back')}</button>
</div> </div>
${getStyles()} ${getStyles()}
`; `;
...@@ -51,7 +52,7 @@ function renderMenu(el) { ...@@ -51,7 +52,7 @@ function renderMenu(el) {
el.querySelector('#btn-online').onclick = () => { el.querySelector('#btn-online').onclick = () => {
audio.play('click'); audio.play('click');
if (store.get('auth.isGuest')) { if (store.get('auth.isGuest')) {
bus.emit('toast', { text: 'سجّل دخولك للعب أونلاين' }); bus.emit('toast', { text: t('play.login_required_online') });
return; return;
} }
scene.replace('backgammon-room', { mode: 'setup', type: 'online' }); scene.replace('backgammon-room', { mode: 'setup', type: 'online' });
...@@ -59,7 +60,7 @@ function renderMenu(el) { ...@@ -59,7 +60,7 @@ function renderMenu(el) {
el.querySelector('#btn-friend').onclick = () => { el.querySelector('#btn-friend').onclick = () => {
audio.play('click'); audio.play('click');
if (store.get('auth.isGuest')) { if (store.get('auth.isGuest')) {
bus.emit('toast', { text: 'سجّل دخولك لتحدي صديق' }); bus.emit('toast', { text: t('play.login_required_friend') });
return; return;
} }
scene.push('challenge-friend', { game: 'backgammon' }); scene.push('challenge-friend', { game: 'backgammon' });
...@@ -77,44 +78,44 @@ function renderSetup(el, params) { ...@@ -77,44 +78,44 @@ function renderSetup(el, params) {
el.innerHTML = ` el.innerHTML = `
<div class="bg-wrap"> <div class="bg-wrap">
<div class="bg-hero" style="margin-bottom:16px;"> <div class="bg-hero" style="margin-bottom:16px;">
<h2 class="bg-title" style="font-size:20px;">${isOnline ? 'إعدادات الأونلاين' : 'إعدادات اللعبة'}</h2> <h2 class="bg-title" style="font-size:20px;">${isOnline ? t('backgammon.settings_online') : t('backgammon.settings_game')}</h2>
</div> </div>
<!-- Variant --> <!-- Variant -->
<div class="bg-section"> <div class="bg-section">
<div class="bg-section-title">نوع اللعبة</div> <div class="bg-section-title">${t('backgammon.game_type')}</div>
<div class="bg-grid" id="variant-grid"> <div class="bg-grid" id="variant-grid">
<button class="bg-chip bg-chip-active" data-variant="sheshbesh"> <button class="bg-chip bg-chip-active" data-variant="sheshbesh">
<span class="bg-chip-label">شيش بيش</span> <span class="bg-chip-label">${t('backgammon.sheshbesh')}</span>
</button> </button>
<button class="bg-chip" data-variant="mahbousa"> <button class="bg-chip" data-variant="mahbousa">
<span class="bg-chip-label">محبوسة</span> <span class="bg-chip-label">${t('backgammon.mahbousa')}</span>
</button> </button>
<button class="bg-chip" data-variant="thirtyone"> <button class="bg-chip" data-variant="thirtyone">
<span class="bg-chip-label">٣١</span> <span class="bg-chip-label">${t('backgammon.thirtyone')}</span>
</button> </button>
</div> </div>
</div> </div>
<!-- Match Length --> <!-- Match Length -->
<div class="bg-section"> <div class="bg-section">
<div class="bg-section-title">طول الماتش</div> <div class="bg-section-title">${t('backgammon.match_length')}</div>
<div class="bg-grid" id="length-grid"> <div class="bg-grid" id="length-grid">
<button class="bg-chip" data-len="1"> <button class="bg-chip" data-len="1">
<span class="bg-chip-num">1</span> <span class="bg-chip-num">1</span>
<span class="bg-chip-label">سريع</span> <span class="bg-chip-label">${t('backgammon.short_match')}</span>
</button> </button>
<button class="bg-chip bg-chip-active" data-len="3"> <button class="bg-chip bg-chip-active" data-len="3">
<span class="bg-chip-num">3</span> <span class="bg-chip-num">3</span>
<span class="bg-chip-label">قصير</span> <span class="bg-chip-label">${t('backgammon.medium_match')}</span>
</button> </button>
<button class="bg-chip" data-len="5"> <button class="bg-chip" data-len="5">
<span class="bg-chip-num">5</span> <span class="bg-chip-num">5</span>
<span class="bg-chip-label">عادي</span> <span class="bg-chip-label">${t('backgammon.normal_match')}</span>
</button> </button>
<button class="bg-chip" data-len="7"> <button class="bg-chip" data-len="7">
<span class="bg-chip-num">7</span> <span class="bg-chip-num">7</span>
<span class="bg-chip-label">طويل</span> <span class="bg-chip-label">${t('backgammon.long_match')}</span>
</button> </button>
</div> </div>
</div> </div>
...@@ -122,19 +123,19 @@ function renderSetup(el, params) { ...@@ -122,19 +123,19 @@ function renderSetup(el, params) {
${!isOnline ? ` ${!isOnline ? `
<!-- Bot Difficulty --> <!-- Bot Difficulty -->
<div class="bg-section"> <div class="bg-section">
<div class="bg-section-title">مستوى البوت</div> <div class="bg-section-title">${t('ludo.bot_level')}</div>
<div class="bg-grid" id="difficulty-grid"> <div class="bg-grid" id="difficulty-grid">
<button class="bg-chip" data-diff="easy"> <button class="bg-chip" data-diff="easy">
<span>😊</span> <span>😊</span>
<span class="bg-chip-label">سهل</span> <span class="bg-chip-label">${t('ludo.easy')}</span>
</button> </button>
<button class="bg-chip bg-chip-active" data-diff="medium"> <button class="bg-chip bg-chip-active" data-diff="medium">
<span>🧐</span> <span>🧐</span>
<span class="bg-chip-label">متوسط</span> <span class="bg-chip-label">${t('ludo.medium')}</span>
</button> </button>
<button class="bg-chip" data-diff="hard"> <button class="bg-chip" data-diff="hard">
<span>🧠</span> <span>🧠</span>
<span class="bg-chip-label">صعب</span> <span class="bg-chip-label">${t('ludo.hard')}</span>
</button> </button>
</div> </div>
</div> </div>
...@@ -142,21 +143,21 @@ function renderSetup(el, params) { ...@@ -142,21 +143,21 @@ function renderSetup(el, params) {
<!-- Doubling Cube Toggle --> <!-- Doubling Cube Toggle -->
<div class="bg-section"> <div class="bg-section">
<div class="bg-section-title">مكعب المضاعفة</div> <div class="bg-section-title">${t('backgammon.doubling_cube')}</div>
<div class="bg-grid" id="cube-grid"> <div class="bg-grid" id="cube-grid">
<button class="bg-chip bg-chip-active" data-cube="on"> <button class="bg-chip bg-chip-active" data-cube="on">
<span class="bg-chip-label">مفعّل</span> <span class="bg-chip-label">${t('backgammon.enabled')}</span>
</button> </button>
<button class="bg-chip" data-cube="off"> <button class="bg-chip" data-cube="off">
<span class="bg-chip-label">بدون</span> <span class="bg-chip-label">${t('backgammon.disabled')}</span>
</button> </button>
</div> </div>
</div> </div>
<button class="bg-btn bg-btn-start" id="btn-start"> <button class="bg-btn bg-btn-start" id="btn-start">
${isOnline ? 'ابحث عن مباراة' : 'ابدأ اللعب'} ${isOnline ? t('ludo.search_match') : t('ludo.start_play')}
</button> </button>
<button class="bg-back" id="btn-back-setup">رجوع</button> <button class="bg-back" id="btn-back-setup">${t('common.back')}</button>
</div> </div>
${getStyles()} ${getStyles()}
`; `;
...@@ -200,10 +201,10 @@ function renderSearching(el) { ...@@ -200,10 +201,10 @@ function renderSearching(el) {
<div class="bg-pulse-ring"> <div class="bg-pulse-ring">
<div class="bg-pulse-inner">${emoji('game_die', '🎲', 32)}</div> <div class="bg-pulse-inner">${emoji('game_die', '🎲', 32)}</div>
</div> </div>
<h2 class="bg-title" style="font-size:18px;margin-top:20px;">جاري البحث...</h2> <h2 class="bg-title" style="font-size:18px;margin-top:20px;">${t('backgammon.searching')}</h2>
<p class="bg-subtitle">بنوصّلك بلاعب قريب</p> <p class="bg-subtitle">${t('backgammon.searching_hint')}</p>
</div> </div>
<button class="bg-btn bg-btn-friend" id="btn-cancel" style="max-width:200px;">إلغاء</button> <button class="bg-btn bg-btn-friend" id="btn-cancel" style="max-width:200px;">${t('common.cancel')}</button>
</div> </div>
${getStyles()} ${getStyles()}
`; `;
......
// Emote panel for backgammon — with cooldown + net sync // Emote panel for backgammon — with cooldown + net sync
import * as audio from '../../../core/audio.js'; import * as audio from '../../../core/audio.js';
import * as net from '../../../core/net.js'; import * as net from '../../../core/net.js';
import { t } from '../../../core/i18n.js';
const EMOTES = ['🎲', '😤', '🤔', '👏', '😂', '🔥', '💀', '🫡']; const EMOTES = ['🎲', '😤', '🤔', '👏', '😂', '🔥', '💀', '🫡'];
const PHRASES = ['!لعبة حلوة', '!حركة ممتازة', '!حظ', '...فكّر أسرع', '?ريماتش', 'gg wp']; const PHRASES = [
() => t('emote.nice_game'),
() => t('emote.great_move'),
() => t('emote.lucky'),
() => t('emote.think_faster'),
() => t('emote.rematch'),
() => 'gg wp'
];
const COOLDOWN = 3000; const COOLDOWN = 3000;
let lastEmoteTime = 0; let lastEmoteTime = 0;
...@@ -17,7 +25,7 @@ export function createEmotePanel(container, options = {}) { ...@@ -17,7 +25,7 @@ export function createEmotePanel(container, options = {}) {
panel.innerHTML = ` panel.innerHTML = `
<div class="bgg-emotes">${EMOTES.map(e => `<button class="bgg-emote-btn">${e}</button>`).join('')}</div> <div class="bgg-emotes">${EMOTES.map(e => `<button class="bgg-emote-btn">${e}</button>`).join('')}</div>
<div class="bgg-phrases">${PHRASES.map(p => `<button class="bgg-phrase-btn">${p}</button>`).join('')}</div> <div class="bgg-phrases">${PHRASES.map(p => `<button class="bgg-phrase-btn">${p()}</button>`).join('')}</div>
`; `;
panel.querySelectorAll('.bgg-emote-btn').forEach(btn => { panel.querySelectorAll('.bgg-emote-btn').forEach(btn => {
......
// In-game emote system for chess // In-game emote system for chess
// Preset emotes that both players can send during a match // Preset emotes that both players can send during a match
import { t } from '../../../core/i18n.js';
const EMOTES = [ const EMOTES = [
{ key: 'gg', emoji: '🤝', label: 'GG' }, { key: 'gg', emoji: '🤝', label: 'GG' },
{ key: 'good_move', emoji: '👏', label: 'نقلة ممتازة' }, { key: 'good_move', emoji: '👏', get label() { return t('emote.good_move'); } },
{ key: 'think', emoji: '🤔', label: 'يفكر...' }, { key: 'think', emoji: '🤔', get label() { return t('emote.think'); } },
{ key: 'hurry', emoji: '⏱️', label: 'أسرع' }, { key: 'hurry', emoji: '⏱️', get label() { return t('emote.hurry'); } },
{ key: 'wow', emoji: '😮', label: 'واو!' }, { key: 'wow', emoji: '😮', get label() { return t('emote.wow'); } },
{ key: 'laugh', emoji: '😂', label: 'هههه' }, { key: 'laugh', emoji: '😂', get label() { return t('emote.laugh'); } },
{ key: 'angry', emoji: '😤', label: 'غاضب' }, { key: 'angry', emoji: '😤', get label() { return t('emote.angry'); } },
{ key: 'hello', emoji: '👋', label: 'مرحبا' }, { key: 'hello', emoji: '👋', get label() { return t('emote.hello'); } },
]; ];
let emoteBar = null; let emoteBar = null;
......
// Rating history graph — canvas sparkline with interactive hover // Rating history graph — canvas sparkline with interactive hover
import { createCanvas, clear } from '../../../core/canvas.js'; import { createCanvas, clear } from '../../../core/canvas.js';
import { t } from '../../../core/i18n.js';
export function renderRatingGraph(container, history, options = {}) { export function renderRatingGraph(container, history, options = {}) {
const { width = 340, height = 140, color = '#3B82F6', bgColor = '#0f0f1e', gridColor = 'rgba(255,255,255,0.05)' } = options; const { width = 340, height = 140, color = '#3B82F6', bgColor = '#0f0f1e', gridColor = 'rgba(255,255,255,0.05)' } = options;
container.innerHTML = ''; container.innerHTML = '';
if (!history || history.length < 2) { if (!history || history.length < 2) {
container.innerHTML = `<div style="height:${height}px;display:flex;align-items:center;justify-content:center;color:#64748b;font-size:12px;">بيانات غير كافية</div>`; container.innerHTML = `<div style="height:${height}px;display:flex;align-items:center;justify-content:center;color:#64748b;font-size:12px;">${t('chess.insufficient_data')}</div>`;
return; return;
} }
...@@ -112,5 +113,5 @@ export function renderRatingGraph(container, history, options = {}) { ...@@ -112,5 +113,5 @@ export function renderRatingGraph(container, history, options = {}) {
ctx.fillStyle = '#64748b'; ctx.fillStyle = '#64748b';
ctx.font = '9px Inter, sans-serif'; ctx.font = '9px Inter, sans-serif';
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.fillText(`${ratings.length} مباراة`, width / 2, height - 5); ctx.fillText(`${ratings.length} ${t('chess.games_count')}`, width / 2, height - 5);
} }
// Move Classification Engine // Move Classification Engine
// Classifies moves as: brilliant, great, best, good, book, inaccuracy, mistake, blunder // Classifies moves as: brilliant, great, best, good, book, inaccuracy, mistake, blunder
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
import { t } from '../../../core/i18n.js';
const THRESHOLDS = { const THRESHOLDS = {
blunder: 2.0, // Lost 2+ pawns worth of eval blunder: 2.0, // Lost 2+ pawns worth of eval
...@@ -66,11 +67,11 @@ export function calculateAccuracy(classifications) { ...@@ -66,11 +67,11 @@ export function calculateAccuracy(classifications) {
} }
export function getAccuracyLabel(accuracy) { export function getAccuracyLabel(accuracy) {
if (accuracy >= 95) return { label: 'ممتاز', labelEn: 'Excellent', color: '#10B981' }; if (accuracy >= 95) return { label: t('classifier.excellent'), labelEn: 'Excellent', color: '#10B981' };
if (accuracy >= 85) return { label: 'جيد جداً', labelEn: 'Great', color: '#3B82F6' }; if (accuracy >= 85) return { label: t('classifier.great'), labelEn: 'Great', color: '#3B82F6' };
if (accuracy >= 70) return { label: 'جيد', labelEn: 'Good', color: '#FBBF24' }; if (accuracy >= 70) return { label: t('classifier.good'), labelEn: 'Good', color: '#FBBF24' };
if (accuracy >= 50) return { label: 'متوسط', labelEn: 'Average', color: '#F97316' }; if (accuracy >= 50) return { label: t('classifier.average'), labelEn: 'Average', color: '#F97316' };
return { label: 'ضعيف', labelEn: 'Poor', color: '#EF4444' }; return { label: t('classifier.poor'), labelEn: 'Poor', color: '#EF4444' };
} }
export function getMoveClassSummary(classifications) { export function getMoveClassSummary(classifications) {
......
...@@ -23,7 +23,7 @@ export function mountAnalysis(el, params) { ...@@ -23,7 +23,7 @@ export function mountAnalysis(el, params) {
<div style="display:flex;flex-direction:column;height:100%;background:#1a1a2e;"> <div style="display:flex;flex-direction:column;height:100%;background:#1a1a2e;">
<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);"> <div style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);">
<button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">← ${t('game.back')}</button> <button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">← ${t('game.back')}</button>
<span style="font-size:14px;font-weight:700;color:#f8fafc;">${emoji('chart', '📊', 14)} تحليل المباراة</span> <span style="font-size:14px;font-weight:700;color:#f8fafc;">${emoji('chart', '📊', 14)} ${t('analysis.title')}</span>
<div style="width:60px;"></div> <div style="width:60px;"></div>
</div> </div>
...@@ -39,12 +39,12 @@ export function mountAnalysis(el, params) { ...@@ -39,12 +39,12 @@ export function mountAnalysis(el, params) {
<!-- Analysis lines + Opening explorer --> <!-- Analysis lines + Opening explorer -->
<div id="analysis-lines" style="padding:6px 12px;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.04);min-height:50px;max-height:90px;overflow-y:auto;font-size:12px;"> <div id="analysis-lines" style="padding:6px 12px;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.04);min-height:50px;max-height:90px;overflow-y:auto;font-size:12px;">
<div style="color:#64748b;font-size:12px;text-align:center;">جاري التحليل...</div> <div style="color:#64748b;font-size:12px;text-align:center;">${t('analysis.analyzing')}</div>
</div> </div>
<!-- Timeline scrubber --> <!-- Timeline scrubber -->
<div style="padding:8px 12px;background:#0a0a18;border-top:1px solid rgba(255,255,255,0.04);"> <div style="padding:8px 12px;background:#0a0a18;border-top:1px solid rgba(255,255,255,0.04);">
<div id="move-display" style="text-align:center;font-family:Inter,monospace;font-size:13px;font-weight:600;color:#f8fafc;margin-bottom:6px;">بداية</div> <div id="move-display" style="text-align:center;font-family:Inter,monospace;font-size:13px;font-weight:600;color:#f8fafc;margin-bottom:6px;">${t('analysis.start')}</div>
<div id="scrubber-track" style="position:relative;height:32px;background:#1a1a2e;border-radius:8px;cursor:pointer;touch-action:none;user-select:none;"> <div id="scrubber-track" style="position:relative;height:32px;background:#1a1a2e;border-radius:8px;cursor:pointer;touch-action:none;user-select:none;">
<div id="scrubber-fill" style="position:absolute;top:0;right:0;height:100%;background:linear-gradient(90deg,#2563EB,#3B82F6);border-radius:8px;width:0%;transition:width 0.15s;"></div> <div id="scrubber-fill" style="position:absolute;top:0;right:0;height:100%;background:linear-gradient(90deg,#2563EB,#3B82F6);border-radius:8px;width:0%;transition:width 0.15s;"></div>
<div id="scrubber-thumb" style="position:absolute;top:50%;transform:translate(50%,-50%);right:0%;width:18px;height:18px;background:#f8fafc;border-radius:50%;box-shadow:0 2px 6px rgba(0,0,0,0.4);transition:right 0.15s;"></div> <div id="scrubber-thumb" style="position:absolute;top:50%;transform:translate(50%,-50%);right:0%;width:18px;height:18px;background:#f8fafc;border-radius:50%;box-shadow:0 2px 6px rgba(0,0,0,0.4);transition:right 0.15s;"></div>
...@@ -107,7 +107,7 @@ export function mountAnalysis(el, params) { ...@@ -107,7 +107,7 @@ export function mountAnalysis(el, params) {
function renderMoveChips(el, moves) { function renderMoveChips(el, moves) {
const container = el.querySelector('#analysis-moves'); const container = el.querySelector('#analysis-moves');
let html = '<span class="move-chip active" data-idx="0">بداية</span>'; let html = `<span class="move-chip active" data-idx="0">${t('analysis.start')}</span>`;
for (let i = 0; i < moves.length; i += 2) { for (let i = 0; i < moves.length; i += 2) {
const num = Math.floor(i / 2) + 1; const num = Math.floor(i / 2) + 1;
html += `<span style="color:#475569;font-size:10px;">${num}.</span>`; html += `<span style="color:#475569;font-size:10px;">${num}.</span>`;
...@@ -143,7 +143,7 @@ function goToMove(el, moves, idx) { ...@@ -143,7 +143,7 @@ function goToMove(el, moves, idx) {
// Update display // Update display
const display = el.querySelector('#move-display'); const display = el.querySelector('#move-display');
if (idx === 0) display.textContent = 'بداية'; if (idx === 0) display.textContent = t('analysis.start');
else display.textContent = `${Math.ceil(idx / 2)}. ${moves[idx - 1]?.san || ''}`; else display.textContent = `${Math.ceil(idx / 2)}. ${moves[idx - 1]?.san || ''}`;
// Update scrubber position // Update scrubber position
...@@ -168,7 +168,7 @@ function goToMove(el, moves, idx) { ...@@ -168,7 +168,7 @@ function goToMove(el, moves, idx) {
async function analyzePosition(el, fen) { async function analyzePosition(el, fen) {
const linesContainer = el.querySelector('#analysis-lines'); const linesContainer = el.querySelector('#analysis-lines');
linesContainer.innerHTML = '<div style="color:#64748b;font-size:12px;text-align:center;">جاري التحليل...</div>'; linesContainer.innerHTML = `<div style="color:#64748b;font-size:12px;text-align:center;">${t('analysis.analyzing')}</div>`;
// Show opening explorer if data exists // Show opening explorer if data exists
const explorerData = lookup(fen); const explorerData = lookup(fen);
...@@ -181,8 +181,8 @@ async function analyzePosition(el, fen) { ...@@ -181,8 +181,8 @@ async function analyzePosition(el, fen) {
</div>` </div>`
).join(''); ).join('');
linesContainer.innerHTML = `<div style="border-bottom:1px solid rgba(255,255,255,0.05);padding-bottom:4px;margin-bottom:4px;"> linesContainer.innerHTML = `<div style="border-bottom:1px solid rgba(255,255,255,0.05);padding-bottom:4px;margin-bottom:4px;">
<div style="font-size:9px;color:#64748b;margin-bottom:2px;">${emoji('book', '📖', 9)} كتاب الافتتاحات</div>${explorerHtml}</div> <div style="font-size:9px;color:#64748b;margin-bottom:2px;">${emoji('book', '📖', 9)} ${t('analysis.openings_book')}</div>${explorerHtml}</div>
<div style="color:#64748b;font-size:11px;text-align:center;">جاري تحليل المحرك...</div>`; <div style="color:#64748b;font-size:11px;text-align:center;">${t('analysis.engine_analyzing')}</div>`;
} }
try { try {
...@@ -193,7 +193,7 @@ async function analyzePosition(el, fen) { ...@@ -193,7 +193,7 @@ async function analyzePosition(el, fen) {
} }
} catch (e) { } catch (e) {
if (!explorerData) { if (!explorerData) {
linesContainer.innerHTML = '<div style="color:#ef4444;font-size:12px;text-align:center;">فشل التحليل</div>'; linesContainer.innerHTML = `<div style="color:#ef4444;font-size:12px;text-align:center;">${t('analysis.failed')}</div>`;
} }
} }
} }
...@@ -204,7 +204,7 @@ function renderAnalysisLines(el, lines, fen, explorerData) { ...@@ -204,7 +204,7 @@ function renderAnalysisLines(el, lines, fen, explorerData) {
let explorerHtml = ''; let explorerHtml = '';
if (explorerData) { if (explorerData) {
explorerHtml = `<div style="border-bottom:1px solid rgba(255,255,255,0.05);padding-bottom:4px;margin-bottom:4px;"> explorerHtml = `<div style="border-bottom:1px solid rgba(255,255,255,0.05);padding-bottom:4px;margin-bottom:4px;">
<div style="font-size:9px;color:#64748b;margin-bottom:2px;">${emoji('book', '📖', 9)} كتاب الافتتاحات</div> <div style="font-size:9px;color:#64748b;margin-bottom:2px;">${emoji('book', '📖', 9)} ${t('analysis.openings_book')}</div>
${explorerData.slice(0, 3).map(e => ${explorerData.slice(0, 3).map(e =>
`<div style="display:flex;align-items:center;gap:6px;padding:1px 0;"> `<div style="display:flex;align-items:center;gap:6px;padding:1px 0;">
<span style="font-size:11px;font-weight:600;color:#f8fafc;min-width:32px;font-family:Inter,monospace;">${e.move}</span> <span style="font-size:11px;font-weight:600;color:#f8fafc;min-width:32px;font-family:Inter,monospace;">${e.move}</span>
......
...@@ -897,7 +897,7 @@ function fetchAndRenderOpponent(el, oppId) { ...@@ -897,7 +897,7 @@ function fetchAndRenderOpponent(el, oppId) {
const nameEl = el.querySelector('#opponent-name'); const nameEl = el.querySelector('#opponent-name');
const levelEl = el.querySelector('#opponent-level'); const levelEl = el.querySelector('#opponent-level');
const avatarEl = el.querySelector('#opponent-avatar'); const avatarEl = el.querySelector('#opponent-avatar');
if (nameEl) nameEl.textContent = opp.display_name || opp.username || 'خصم'; if (nameEl) nameEl.textContent = opp.display_name || opp.username || t('common.opponent');
if (levelEl) levelEl.textContent = `Lv.${opp.level || 1}`; if (levelEl) levelEl.textContent = `Lv.${opp.level || 1}`;
if (avatarEl && opp.avatar_url) { if (avatarEl && opp.avatar_url) {
avatarEl.innerHTML = `<img src="${opp.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`; avatarEl.innerHTML = `<img src="${opp.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`;
......
...@@ -40,7 +40,7 @@ function renderHistory(el, matches) { ...@@ -40,7 +40,7 @@ function renderHistory(el, matches) {
list.innerHTML = ` list.innerHTML = `
<div style="text-align:center;padding:40px;"> <div style="text-align:center;padding:40px;">
<div style="font-size:40px;margin-bottom:8px;opacity:0.4;">${emoji('clipboard', '📋', 40)}</div> <div style="font-size:40px;margin-bottom:8px;opacity:0.4;">${emoji('clipboard', '📋', 40)}</div>
<div style="font-size:14px;color:#64748b;">لا توجد مباريات بعد — العب لتظهر هنا</div> <div style="font-size:14px;color:#64748b;">${t('history.no_matches')}</div>
</div>`; </div>`;
return; return;
} }
...@@ -49,16 +49,16 @@ function renderHistory(el, matches) { ...@@ -49,16 +49,16 @@ function renderHistory(el, matches) {
const isWhite = m.white_player_id === userId; const isWhite = m.white_player_id === userId;
const myResult = getMyResult(m.result, isWhite); const myResult = getMyResult(m.result, isWhite);
const resultConfig = { const resultConfig = {
win: { label: 'فوز', color: '#34D399', icon: emoji('trophy', '🏆', 20) }, win: { label: t('history.win'), color: '#34D399', icon: emoji('trophy', '🏆', 20) },
loss: { label: 'خسارة', color: '#F87171', icon: emoji('skull', '💀', 20) }, loss: { label: t('history.loss'), color: '#F87171', icon: emoji('skull', '💀', 20) },
draw: { label: 'تعادل', color: '#E4AC38', icon: emoji('handshake', '🤝', 20) }, draw: { label: t('history.draw'), color: '#E4AC38', icon: emoji('handshake', '🤝', 20) },
unknown: { label: '—', color: '#64748b', icon: '•' } unknown: { label: '—', color: '#64748b', icon: '•' }
}; };
const cfg = resultConfig[myResult] || resultConfig.unknown; const cfg = resultConfig[myResult] || resultConfig.unknown;
const ratingChange = isWhite ? m.rating_change_white : m.rating_change_black; const ratingChange = isWhite ? m.rating_change_white : m.rating_change_black;
const ratingStr = ratingChange ? (ratingChange > 0 ? `+${ratingChange}` : `${ratingChange}`) : ''; const ratingStr = ratingChange ? (ratingChange > 0 ? `+${ratingChange}` : `${ratingChange}`) : '';
const timeAgo = formatTimeAgo(m.created_at); const timeAgo = formatTimeAgo(m.created_at);
const opponent = isWhite ? (m.black_name || m.bot_id || 'خصم') : (m.white_name || m.bot_id || 'خصم'); const opponent = isWhite ? (m.black_name || m.bot_id || t('common.opponent')) : (m.white_name || m.bot_id || t('common.opponent'));
const tc = formatTimeControl(m.time_control); const tc = formatTimeControl(m.time_control);
return ` return `
...@@ -102,10 +102,10 @@ function formatTimeAgo(date) { ...@@ -102,10 +102,10 @@ function formatTimeAgo(date) {
if (!date) return ''; if (!date) return '';
const diff = Date.now() - new Date(date).getTime(); const diff = Date.now() - new Date(date).getTime();
const mins = Math.floor(diff / 60000); const mins = Math.floor(diff / 60000);
if (mins < 1) return 'الآن'; if (mins < 1) return t('common.now');
if (mins < 60) return `${mins}د`; if (mins < 60) return t('common.minutes_ago', { n: mins });
const hours = Math.floor(mins / 60); const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}س`; if (hours < 24) return t('common.hours_ago', { n: hours });
const days = Math.floor(hours / 24); const days = Math.floor(hours / 24);
return `${days}ي`; return t('common.days_ago', { n: days });
} }
...@@ -23,12 +23,12 @@ export function mountResult(el, params) { ...@@ -23,12 +23,12 @@ export function mountResult(el, params) {
const cfg = resultConfig[result] || resultConfig.loss; const cfg = resultConfig[result] || resultConfig.loss;
const reasonText = { const reasonText = {
checkmate: 'كش ملك', checkmate: t('game.checkmate'),
stalemate: 'بات (جمود)', stalemate: t('game.stalemate'),
resign: 'استسلام', resign: t('game.resign'),
timeout: 'انتهاء الوقت', timeout: t('game.timeout'),
threefold: 'تكرار ثلاثي', threefold: t('game.threefold'),
insufficient: 'قطع غير كافية' insufficient: t('game.insufficient')
}[reason] || reason; }[reason] || reason;
el.innerHTML = ` el.innerHTML = `
...@@ -40,24 +40,24 @@ export function mountResult(el, params) { ...@@ -40,24 +40,24 @@ export function mountResult(el, params) {
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:32px;font-weight:800;color:${cfg.color};">${cfg.title}</div> <div style="font-size:32px;font-weight:800;color:${cfg.color};">${cfg.title}</div>
<div style="font-size:14px;color:#94a3b8;margin-top:4px;">${reasonText}</div> <div style="font-size:14px;color:#94a3b8;margin-top:4px;">${reasonText}</div>
${moves ? `<div style="font-size:12px;color:#475569;margin-top:2px;">${moves} نقلة</div>` : ''} ${moves ? `<div style="font-size:12px;color:#475569;margin-top:2px;">${t('game.moves_count', { n: moves })}</div>` : ''}
</div> </div>
<!-- Stats Row --> <!-- Stats Row -->
<div style="display:flex;gap:24px;margin-top:8px;"> <div style="display:flex;gap:24px;margin-top:8px;">
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:28px;font-weight:700;color:${cfg.color};font-family:Inter,monospace;">${ratingStr}</div> <div style="font-size:28px;font-weight:700;color:${cfg.color};font-family:Inter,monospace;">${ratingStr}</div>
<div style="font-size:11px;color:#64748b;">تصنيف</div> <div style="font-size:11px;color:#64748b;">${t('game.rating')}</div>
</div> </div>
<div style="width:1px;background:rgba(255,255,255,0.06);"></div> <div style="width:1px;background:rgba(255,255,255,0.06);"></div>
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:28px;font-weight:700;color:#E4AC38;font-family:Inter,monospace;">+${coins}</div> <div style="font-size:28px;font-weight:700;color:#E4AC38;font-family:Inter,monospace;">+${coins}</div>
<div style="font-size:11px;color:#64748b;">عملات</div> <div style="font-size:11px;color:#64748b;">${t('game.coins')}</div>
</div> </div>
<div style="width:1px;background:rgba(255,255,255,0.06);"></div> <div style="width:1px;background:rgba(255,255,255,0.06);"></div>
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:28px;font-weight:700;color:#00FFFF;font-family:Inter,monospace;">+${xp}</div> <div style="font-size:28px;font-weight:700;color:#00FFFF;font-family:Inter,monospace;">+${xp}</div>
<div style="font-size:11px;color:#64748b;">خبرة</div> <div style="font-size:11px;color:#64748b;">${t('game.xp')}</div>
</div> </div>
</div> </div>
...@@ -72,13 +72,13 @@ export function mountResult(el, params) { ...@@ -72,13 +72,13 @@ export function mountResult(el, params) {
<!-- Actions --> <!-- Actions -->
<div style="display:flex;flex-direction:column;gap:8px;width:100%;max-width:280px;margin-top:12px;"> <div style="display:flex;flex-direction:column;gap:8px;width:100%;max-width:280px;margin-top:12px;">
${params.tournamentId ${params.tournamentId
? `<button class="btn btn-primary w-full" id="btn-back-tournament" style="font-size:15px;">${emoji('trophy', '🏆', 15)} العودة للبطولة</button>` ? `<button class="btn btn-primary w-full" id="btn-back-tournament" style="font-size:15px;">${emoji('trophy', '🏆', 15)} ${t('tournament.back_to')}</button>`
: `<button class="btn btn-primary w-full" id="btn-rematch" style="font-size:15px;">${t('game.rematch')}</button>` : `<button class="btn btn-primary w-full" id="btn-rematch" style="font-size:15px;">${t('game.rematch')}</button>`
} }
<button class="btn btn-secondary w-full" id="btn-analyze" style="font-size:13px;">${emoji('chart', '📊', 13)} تحليل المباراة</button> <button class="btn btn-secondary w-full" id="btn-analyze" style="font-size:13px;">${emoji('chart', '📊', 13)} ${t('result.analyze')}</button>
<div style="display:flex;gap:8px;"> <div style="display:flex;gap:8px;">
<button class="btn btn-secondary" id="btn-share" style="flex:1;font-size:12px;">${emoji('share', '📤', 12)} مشاركة PGN</button> <button class="btn btn-secondary" id="btn-share" style="flex:1;font-size:12px;">${emoji('share', '📤', 12)} ${t('result.share_pgn')}</button>
<button class="btn btn-secondary" id="btn-copy-pgn" style="flex:1;font-size:12px;">${emoji('clipboard', '📋', 12)} نسخ</button> <button class="btn btn-secondary" id="btn-copy-pgn" style="flex:1;font-size:12px;">${emoji('clipboard', '📋', 12)} ${t('result.copy_pgn')}</button>
</div> </div>
<button class="btn btn-secondary w-full" id="btn-back" style="font-size:13px;">${t('game.back')}</button> <button class="btn btn-secondary w-full" id="btn-back" style="font-size:13px;">${t('game.back')}</button>
</div> </div>
...@@ -162,9 +162,9 @@ export function mountResult(el, params) { ...@@ -162,9 +162,9 @@ export function mountResult(el, params) {
const pgnText = pgn || formatMoveHistory(moveHistory); const pgnText = pgn || formatMoveHistory(moveHistory);
navigator.clipboard?.writeText(pgnText).then(() => { navigator.clipboard?.writeText(pgnText).then(() => {
const btn = el.querySelector('#btn-copy-pgn'); const btn = el.querySelector('#btn-copy-pgn');
btn.textContent = '✓ تم النسخ'; btn.textContent = t('result.copied');
juice.pulseElement(btn, '#34D399'); juice.pulseElement(btn, '#34D399');
setTimeout(() => { btn.innerHTML = `${emoji('clipboard', '📋', 12)} نسخ`; }, 2000); setTimeout(() => { btn.innerHTML = `${emoji('clipboard', '📋', 12)} ${t('result.copy_pgn')}`; }, 2000);
}); });
}); });
......
...@@ -18,13 +18,13 @@ export async function mountReview(el, params) { ...@@ -18,13 +18,13 @@ export async function mountReview(el, params) {
<div style="display:flex;flex-direction:column;height:100%;background:#1a1a2e;overflow-y:auto;"> <div style="display:flex;flex-direction:column;height:100%;background:#1a1a2e;overflow-y:auto;">
<div style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);"> <div style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);">
<button class="btn btn-secondary" id="back-btn" style="min-height:30px;padding:4px 12px;font-size:12px;">← ${t('game.back')}</button> <button class="btn btn-secondary" id="back-btn" style="min-height:30px;padding:4px 12px;font-size:12px;">← ${t('game.back')}</button>
<span style="font-size:15px;font-weight:700;color:#f8fafc;">${emoji('star', '⭐', 15)} مراجعة المباراة</span> <span style="font-size:15px;font-weight:700;color:#f8fafc;">${emoji('star', '⭐', 15)} ${t('review.title')}</span>
<div style="width:50px;"></div> <div style="width:50px;"></div>
</div> </div>
<!-- Progress --> <!-- Progress -->
<div id="review-progress" style="padding:16px;text-align:center;"> <div id="review-progress" style="padding:16px;text-align:center;">
<div style="font-size:14px;color:#94a3b8;margin-bottom:8px;">جاري تحليل كل نقلة...</div> <div style="font-size:14px;color:#94a3b8;margin-bottom:8px;">${t('review.analyzing_moves')}</div>
<div style="background:#1e1e3a;border-radius:6px;height:8px;overflow:hidden;"> <div style="background:#1e1e3a;border-radius:6px;height:8px;overflow:hidden;">
<div id="progress-bar" style="height:100%;background:#E4AC38;width:0%;transition:width 0.3s;border-radius:6px;"></div> <div id="progress-bar" style="height:100%;background:#E4AC38;width:0%;transition:width 0.3s;border-radius:6px;"></div>
</div> </div>
...@@ -126,7 +126,7 @@ function renderReview(el, results, moves, playerColor) { ...@@ -126,7 +126,7 @@ function renderReview(el, results, moves, playerColor) {
const playerLabel = getAccuracyLabel(playerAccuracy); const playerLabel = getAccuracyLabel(playerAccuracy);
const opponentLabel = getAccuracyLabel(opponentAccuracy); const opponentLabel = getAccuracyLabel(opponentAccuracy);
const opening = getOpeningName(moves) || 'غير معروف'; const opening = getOpeningName(moves) || t('review.unknown_opening');
content.innerHTML = ` content.innerHTML = `
<!-- Eval Graph --> <!-- Eval Graph -->
...@@ -134,13 +134,13 @@ function renderReview(el, results, moves, playerColor) { ...@@ -134,13 +134,13 @@ function renderReview(el, results, moves, playerColor) {
<!-- Accuracy Comparison --> <!-- Accuracy Comparison -->
<div style="background:#0f0f1e;border-radius:10px;padding:16px;margin-bottom:12px;"> <div style="background:#0f0f1e;border-radius:10px;padding:16px;margin-bottom:12px;">
<div style="text-align:center;font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:12px;">${emoji('star', '⭐', 15)} مراجعة المباراة</div> <div style="text-align:center;font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:12px;">${emoji('star', '⭐', 15)} ${t('review.title')}</div>
<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;"> <div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;">
<!-- Player accuracy --> <!-- Player accuracy -->
<div style="flex:1;text-align:center;"> <div style="flex:1;text-align:center;">
<div style="font-size:28px;font-weight:800;color:${playerLabel.color};font-family:Inter,monospace;">${playerAccuracy}%</div> <div style="font-size:28px;font-weight:800;color:${playerLabel.color};font-family:Inter,monospace;">${playerAccuracy}%</div>
<div style="font-size:11px;color:#94a3b8;">أنت</div> <div style="font-size:11px;color:#94a3b8;">${t('common.you')}</div>
<div style="height:6px;background:#1e1e3a;border-radius:3px;margin-top:6px;overflow:hidden;"> <div style="height:6px;background:#1e1e3a;border-radius:3px;margin-top:6px;overflow:hidden;">
<div style="height:100%;width:${playerAccuracy}%;background:${playerLabel.color};border-radius:3px;"></div> <div style="height:100%;width:${playerAccuracy}%;background:${playerLabel.color};border-radius:3px;"></div>
</div> </div>
...@@ -151,7 +151,7 @@ function renderReview(el, results, moves, playerColor) { ...@@ -151,7 +151,7 @@ function renderReview(el, results, moves, playerColor) {
<!-- Opponent accuracy --> <!-- Opponent accuracy -->
<div style="flex:1;text-align:center;"> <div style="flex:1;text-align:center;">
<div style="font-size:28px;font-weight:800;color:${opponentLabel.color};font-family:Inter,monospace;">${opponentAccuracy}%</div> <div style="font-size:28px;font-weight:800;color:${opponentLabel.color};font-family:Inter,monospace;">${opponentAccuracy}%</div>
<div style="font-size:11px;color:#94a3b8;">الخصم</div> <div style="font-size:11px;color:#94a3b8;">${t('common.opponent')}</div>
<div style="height:6px;background:#1e1e3a;border-radius:3px;margin-top:6px;overflow:hidden;"> <div style="height:6px;background:#1e1e3a;border-radius:3px;margin-top:6px;overflow:hidden;">
<div style="height:100%;width:${opponentAccuracy}%;background:${opponentLabel.color};border-radius:3px;"></div> <div style="height:100%;width:${opponentAccuracy}%;background:${opponentLabel.color};border-radius:3px;"></div>
</div> </div>
...@@ -164,30 +164,30 @@ function renderReview(el, results, moves, playerColor) { ...@@ -164,30 +164,30 @@ function renderReview(el, results, moves, playerColor) {
<!-- Move Classification Breakdown --> <!-- Move Classification Breakdown -->
<div style="background:#0f0f1e;border-radius:10px;padding:14px;margin-bottom:12px;"> <div style="background:#0f0f1e;border-radius:10px;padding:14px;margin-bottom:12px;">
<div style="font-size:13px;font-weight:700;color:#f8fafc;margin-bottom:10px;text-align:center;">تصنيف النقلات</div> <div style="font-size:13px;font-weight:700;color:#f8fafc;margin-bottom:10px;text-align:center;">${t('review.move_classification')}</div>
<table style="width:100%;border-collapse:collapse;font-size:12px;"> <table style="width:100%;border-collapse:collapse;font-size:12px;">
<thead> <thead>
<tr style="color:#64748b;"> <tr style="color:#64748b;">
<th style="text-align:right;padding:3px 0;font-weight:600;">أنت</th> <th style="text-align:right;padding:3px 0;font-weight:600;">${t('common.you')}</th>
<th style="text-align:center;padding:3px 8px;"></th> <th style="text-align:center;padding:3px 8px;"></th>
<th style="text-align:left;padding:3px 0;font-weight:600;">الخصم</th> <th style="text-align:left;padding:3px 0;font-weight:600;">${t('common.opponent')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
${renderClassRow('brilliant', '!! رائعة', '#06B6D4', whiteSummary, blackSummary, playerColor)} ${renderClassRow('brilliant', t('review.brilliant'), '#06B6D4', whiteSummary, blackSummary, playerColor)}
${renderClassRow('great', '! ممتازة', '#3B82F6', whiteSummary, blackSummary, playerColor)} ${renderClassRow('great', t('review.great'), '#3B82F6', whiteSummary, blackSummary, playerColor)}
${renderClassRow('best', emoji('best_move_star', '★', 12) + ' أفضل نقلة', '#10B981', whiteSummary, blackSummary, playerColor)} ${renderClassRow('best', emoji('best_move_star', '★', 12) + ' ' + t('review.best'), '#10B981', whiteSummary, blackSummary, playerColor)}
${renderClassRow('good', '✓ جيدة', '#94a3b8', whiteSummary, blackSummary, playerColor)} ${renderClassRow('good', t('review.good'), '#94a3b8', whiteSummary, blackSummary, playerColor)}
${renderClassRow('book', emoji('book', '📖', 12) + ' نظرية', '#94a3b8', whiteSummary, blackSummary, playerColor)} ${renderClassRow('book', emoji('book', '📖', 12) + ' ' + t('review.book'), '#94a3b8', whiteSummary, blackSummary, playerColor)}
${renderClassRow('inaccuracy', '?! عدم دقة', '#FBBF24', whiteSummary, blackSummary, playerColor)} ${renderClassRow('inaccuracy', t('review.inaccuracy'), '#FBBF24', whiteSummary, blackSummary, playerColor)}
${renderClassRow('mistake', '? خطأ', '#F97316', whiteSummary, blackSummary, playerColor)} ${renderClassRow('mistake', t('review.mistake'), '#F97316', whiteSummary, blackSummary, playerColor)}
${renderClassRow('blunder', '?? خطأ فادح', '#EF4444', whiteSummary, blackSummary, playerColor)} ${renderClassRow('blunder', t('review.blunder'), '#EF4444', whiteSummary, blackSummary, playerColor)}
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Deep analysis button --> <!-- Deep analysis button -->
<button class="btn btn-secondary w-full" id="btn-full-analysis" style="font-size:13px;margin-bottom:12px;">${emoji('chart', '📊', 13)} التحليل التفصيلي (نقلة بنقلة)</button> <button class="btn btn-secondary w-full" id="btn-full-analysis" style="font-size:13px;margin-bottom:12px;">${emoji('chart', '📊', 13)} ${t('review.full_analysis')}</button>
`; `;
// Render eval graph // Render eval graph
......
...@@ -22,13 +22,13 @@ export async function mountSpectate(el, params = {}) { ...@@ -22,13 +22,13 @@ export async function mountSpectate(el, params = {}) {
<button class="btn btn-secondary" id="back-btn" style="width:34px;height:34px;padding:0;">←</button> <button class="btn btn-secondary" id="back-btn" style="width:34px;height:34px;padding:0;">←</button>
<div style="flex:1;display:flex;align-items:center;gap:8px;"> <div style="flex:1;display:flex;align-items:center;gap:8px;">
<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#EF4444;animation:spectatePulse 1.5s infinite;"></span> <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#EF4444;animation:spectatePulse 1.5s infinite;"></span>
<span style="font-size:13px;font-weight:600;color:#EF4444;">مشاهدة مباشرة</span> <span style="font-size:13px;font-weight:600;color:#EF4444;">${t('spectate.live')}</span>
</div> </div>
</div> </div>
<div class="chess-bar" style="display:flex;align-items:center;justify-content:space-between;padding:8px 14px;"> <div class="chess-bar" style="display:flex;align-items:center;justify-content:space-between;padding:8px 14px;">
<div style="display:flex;align-items:center;gap:8px;"> <div style="display:flex;align-items:center;gap:8px;">
<div style="width:32px;height:32px;border-radius:50%;background:#2a2a4a;display:flex;align-items:center;justify-content:center;">${emoji('person', '👤', 14)}</div> <div style="width:32px;height:32px;border-radius:50%;background:#2a2a4a;display:flex;align-items:center;justify-content:center;">${emoji('person', '👤', 14)}</div>
<span id="black-name" style="font-size:13px;font-weight:600;color:#f8fafc;">أسود</span> <span id="black-name" style="font-size:13px;font-weight:600;color:#f8fafc;">${t('spectate.black')}</span>
</div> </div>
<div id="clock-black" style="font-size:16px;font-weight:700;font-family:'SF Mono',monospace;background:#1a1a2e;padding:4px 10px;border-radius:6px;color:#94a3b8;">--:--</div> <div id="clock-black" style="font-size:16px;font-weight:700;font-family:'SF Mono',monospace;background:#1a1a2e;padding:4px 10px;border-radius:6px;color:#94a3b8;">--:--</div>
</div> </div>
...@@ -36,7 +36,7 @@ export async function mountSpectate(el, params = {}) { ...@@ -36,7 +36,7 @@ export async function mountSpectate(el, params = {}) {
<div class="chess-bar" style="display:flex;align-items:center;justify-content:space-between;padding:8px 14px;"> <div class="chess-bar" style="display:flex;align-items:center;justify-content:space-between;padding:8px 14px;">
<div style="display:flex;align-items:center;gap:8px;"> <div style="display:flex;align-items:center;gap:8px;">
<div style="width:32px;height:32px;border-radius:50%;background:#2a2a4a;display:flex;align-items:center;justify-content:center;">${emoji('person', '👤', 14)}</div> <div style="width:32px;height:32px;border-radius:50%;background:#2a2a4a;display:flex;align-items:center;justify-content:center;">${emoji('person', '👤', 14)}</div>
<span id="white-name" style="font-size:13px;font-weight:600;color:#f8fafc;">أبيض</span> <span id="white-name" style="font-size:13px;font-weight:600;color:#f8fafc;">${t('spectate.white')}</span>
</div> </div>
<div id="clock-white" style="font-size:16px;font-weight:700;font-family:'SF Mono',monospace;background:#1a1a2e;padding:4px 10px;border-radius:6px;color:#94a3b8;">--:--</div> <div id="clock-white" style="font-size:16px;font-weight:700;font-family:'SF Mono',monospace;background:#1a1a2e;padding:4px 10px;border-radius:6px;color:#94a3b8;">--:--</div>
</div> </div>
...@@ -63,7 +63,7 @@ export async function mountSpectate(el, params = {}) { ...@@ -63,7 +63,7 @@ export async function mountSpectate(el, params = {}) {
try { try {
matchData = await net.post('game.php', { action: 'get', match_id: matchId }); matchData = await net.post('game.php', { action: 'get', match_id: matchId });
if (!matchData || matchData.error) { if (!matchData || matchData.error) {
el.querySelector('#board-container').innerHTML = `<div style="color:var(--error);padding:24px;">مباراة غير موجودة</div>`; el.querySelector('#board-container').innerHTML = `<div style="color:var(--error);padding:24px;">${t('game.not_found')}</div>`;
return; return;
} }
} catch (e) { } catch (e) {
...@@ -133,8 +133,8 @@ async function loadPlayerNames(el, whiteId, blackId) { ...@@ -133,8 +133,8 @@ async function loadPlayerNames(el, whiteId, blackId) {
net.get('profile.php', { id: whiteId }), net.get('profile.php', { id: whiteId }),
net.get('profile.php', { id: blackId }) net.get('profile.php', { id: blackId })
]); ]);
if (wp && !wp.error) el.querySelector('#white-name').textContent = wp.display_name || 'أبيض'; if (wp && !wp.error) el.querySelector('#white-name').textContent = wp.display_name || t('spectate.white');
if (bp && !bp.error) el.querySelector('#black-name').textContent = bp.display_name || 'أسود'; if (bp && !bp.error) el.querySelector('#black-name').textContent = bp.display_name || t('spectate.black');
} catch (e) {} } catch (e) {}
} }
...@@ -160,9 +160,9 @@ function renderMoveList(el, moves) { ...@@ -160,9 +160,9 @@ function renderMoveList(el, moves) {
} }
function showResult(el, result) { function showResult(el, result) {
const text = result === 'white_wins' ? 'فاز الأبيض' : const text = result === 'white_wins' ? t('spectate.white_wins') :
result === 'black_wins' ? 'فاز الأسود' : result === 'black_wins' ? t('spectate.black_wins') :
result === 'draw' ? 'تعادل' : 'انتهت المباراة'; result === 'draw' ? t('game.draw_game') : t('spectate.game_over');
const overlay = document.createElement('div'); const overlay = document.createElement('div');
overlay.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.9);color:#f8fafc;padding:16px 32px;border-radius:12px;font-size:16px;font-weight:700;z-index:30;text-align:center;'; overlay.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.9);color:#f8fafc;padding:16px 32px;border-radius:12px;font-size:16px;font-weight:700;z-index:30;text-align:center;';
overlay.textContent = text; overlay.textContent = text;
......
...@@ -2,6 +2,7 @@ import { createCanvas, clear } from '../../../core/canvas.js'; ...@@ -2,6 +2,7 @@ import { createCanvas, clear } from '../../../core/canvas.js';
import { computeLayout, hitTestEndpoint, getSnapRadius, getAutoZoom } from '../logic/layout.js'; import { computeLayout, hitTestEndpoint, getSnapRadius, getAutoZoom } from '../logic/layout.js';
import { drawTile, drawEndpointGlow } from './tile-renderer.js'; import { drawTile, drawEndpointGlow } from './tile-renderer.js';
import { TILE_W, TILE_H } from '../logic/layout.js'; import { TILE_W, TILE_H } from '../logic/layout.js';
import { t } from '../../../core/i18n.js';
export class DominoBoard { export class DominoBoard {
constructor(container, options = {}) { constructor(container, options = {}) {
...@@ -257,7 +258,7 @@ export class DominoBoard { ...@@ -257,7 +258,7 @@ export class DominoBoard {
ctx.font = '600 13px system-ui, sans-serif'; ctx.font = '600 13px system-ui, sans-serif';
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.fillText('ضع أول قطعة', this.width / 2, this.height / 2); ctx.fillText(t('game.place_first'), this.width / 2, this.height / 2);
this._drawCenterGlow(ctx); this._drawCenterGlow(ctx);
ctx.restore(); ctx.restore();
return; return;
......
...@@ -111,10 +111,10 @@ function buildLayout(mode) { ...@@ -111,10 +111,10 @@ function buildLayout(mode) {
<div class="dg-opp-left"> <div class="dg-opp-left">
<div id="opp-avatar" class="dg-avatar">${isLive ? '👤' : '🤖'}</div> <div id="opp-avatar" class="dg-avatar">${isLive ? '👤' : '🤖'}</div>
<div class="dg-opp-info"> <div class="dg-opp-info">
<div id="opp-name" class="dg-opp-name">${isLive ? 'خصم' : 'بوت'}</div> <div id="opp-name" class="dg-opp-name">${isLive ? t('common.opponent') : t('game.bot')}</div>
<div class="dg-opp-meta"> <div class="dg-opp-meta">
<span id="opp-count" class="dg-opp-count">7 قطع</span> <span id="opp-count" class="dg-opp-count">${t('common.pieces', { n: 7 })}</span>
<span id="bot-thinking" class="dg-thinking">يفكر<span class="dg-dots"></span></span> <span id="bot-thinking" class="dg-thinking">${t('game.thinking')}<span class="dg-dots"></span></span>
</div> </div>
</div> </div>
</div> </div>
...@@ -140,7 +140,7 @@ function buildLayout(mode) { ...@@ -140,7 +140,7 @@ function buildLayout(mode) {
<div class="dg-status-ribbon"> <div class="dg-status-ribbon">
<div class="dg-score-section"> <div class="dg-score-section">
<div class="dg-score-me"> <div class="dg-score-me">
<span class="dg-score-label">أنت</span> <span class="dg-score-label">${t('common.you')}</span>
<span class="dg-score-value" id="my-score">0</span> <span class="dg-score-value" id="my-score">0</span>
</div> </div>
<div class="dg-score-divider"></div> <div class="dg-score-divider"></div>
...@@ -149,11 +149,11 @@ function buildLayout(mode) { ...@@ -149,11 +149,11 @@ function buildLayout(mode) {
</div> </div>
<div class="dg-score-divider"></div> <div class="dg-score-divider"></div>
<div class="dg-score-opp"> <div class="dg-score-opp">
<span class="dg-score-label">خصم</span> <span class="dg-score-label">${t('common.opponent')}</span>
<span class="dg-score-value" id="opp-score">0</span> <span class="dg-score-value" id="opp-score">0</span>
</div> </div>
</div> </div>
<div id="turn-status" class="dg-turn-status">دورك!</div> <div id="turn-status" class="dg-turn-status">${t('game.your_turn')}</div>
</div> </div>
<!-- Player hand --> <!-- Player hand -->
...@@ -161,10 +161,10 @@ function buildLayout(mode) { ...@@ -161,10 +161,10 @@ function buildLayout(mode) {
<!-- Controls --> <!-- Controls -->
<div id="domino-controls" class="dg-controls"> <div id="domino-controls" class="dg-controls">
<button class="dg-ctrl-btn dg-btn-resign" id="btn-resign">استسلام</button> <button class="dg-ctrl-btn dg-btn-resign" id="btn-resign">${t('game.resign')}</button>
<button class="dg-ctrl-btn dg-btn-emote" id="btn-emote">😄</button> <button class="dg-ctrl-btn dg-btn-emote" id="btn-emote">😄</button>
<button class="dg-ctrl-btn dg-btn-draw" id="btn-draw">سحب من المخزن</button> <button class="dg-ctrl-btn dg-btn-draw" id="btn-draw">${t('domino.draw_boneyard')}</button>
<button class="dg-ctrl-btn dg-btn-pass" id="btn-pass" style="display:none;">تمرير</button> <button class="dg-ctrl-btn dg-btn-pass" id="btn-pass" style="display:none;">${t('domino.pass')}</button>
</div> </div>
</div> </div>
...@@ -517,7 +517,7 @@ function handleLivePollData(el, data) { ...@@ -517,7 +517,7 @@ function handleLivePollData(el, data) {
if (data.opponent_count !== undefined) { if (data.opponent_count !== undefined) {
const oppIdx = 1 - state.myPlayerIndex; const oppIdx = 1 - state.myPlayerIndex;
const oppEl = el.querySelector('#opp-count'); const oppEl = el.querySelector('#opp-count');
if (oppEl) oppEl.textContent = `${data.opponent_count} قطع`; if (oppEl) oppEl.textContent = t('common.pieces', { n: data.opponent_count });
} }
if (gs.scores) { if (gs.scores) {
...@@ -930,21 +930,21 @@ function showRoundOverlay(el, winnerIdx, points) { ...@@ -930,21 +930,21 @@ function showRoundOverlay(el, winnerIdx, points) {
overlay.innerHTML = ` overlay.innerHTML = `
<div style="font-size:48px;">${isMyWin ? emoji('trophy', '🏆', 48) : emoji('skull', '💀', 48)}</div> <div style="font-size:48px;">${isMyWin ? emoji('trophy', '🏆', 48) : emoji('skull', '💀', 48)}</div>
<div style="font-size:20px;font-weight:800;color:${isMyWin ? '#4ade80' : '#fca5a5'};"> <div style="font-size:20px;font-weight:800;color:${isMyWin ? '#4ade80' : '#fca5a5'};">
${isMyWin ? 'فزت بالجولة!' : 'خسرت الجولة'} ${isMyWin ? t('game.round_won') : t('game.round_lost')}
</div> </div>
<div style="font-size:14px;color:#94a3b8;">+${points} نقطة</div> <div style="font-size:14px;color:#94a3b8;">${t('game.points', { n: points })}</div>
<div style="display:flex;gap:16px;margin-top:8px;"> <div style="display:flex;gap:16px;margin-top:8px;">
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:24px;font-weight:800;color:#4ade80;">${state.matchScores[state.myPlayerIndex]}</div> <div style="font-size:24px;font-weight:800;color:#4ade80;">${state.matchScores[state.myPlayerIndex]}</div>
<div style="font-size:11px;color:#6ee7b7;">أنت</div> <div style="font-size:11px;color:#6ee7b7;">${t('common.you')}</div>
</div> </div>
<div style="font-size:20px;color:#475569;align-self:center;">—</div> <div style="font-size:20px;color:#475569;align-self:center;">—</div>
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:24px;font-weight:800;color:#fca5a5;">${state.matchScores[1 - state.myPlayerIndex]}</div> <div style="font-size:24px;font-weight:800;color:#fca5a5;">${state.matchScores[1 - state.myPlayerIndex]}</div>
<div style="font-size:11px;color:#fca5a5;">خصم</div> <div style="font-size:11px;color:#fca5a5;">${t('common.opponent')}</div>
</div> </div>
</div> </div>
<div style="font-size:12px;color:#64748b;margin-top:8px;">الجولة التالية تبدأ...</div> <div style="font-size:12px;color:#64748b;margin-top:8px;">${t('game.round_next')}</div>
`; `;
const wrap = el.querySelector('#domino-wrap'); const wrap = el.querySelector('#domino-wrap');
...@@ -1124,16 +1124,16 @@ function updateUI(el) { ...@@ -1124,16 +1124,16 @@ function updateUI(el) {
const turnEl = el.querySelector('#turn-status'); const turnEl = el.querySelector('#turn-status');
if (turnEl) { if (turnEl) {
if (state.gameOver) { if (state.gameOver) {
turnEl.textContent = 'انتهت الجولة'; turnEl.textContent = t('game.round_end');
turnEl.className = 'dg-turn-status dg-waiting'; turnEl.className = 'dg-turn-status dg-waiting';
} else if (isMyTurn && state.selectedTile) { } else if (isMyTurn && state.selectedTile) {
turnEl.textContent = 'اختر مكان الوضع'; turnEl.textContent = t('game.choose_placement');
turnEl.className = 'dg-turn-status'; turnEl.className = 'dg-turn-status';
} else if (isMyTurn) { } else if (isMyTurn) {
turnEl.textContent = 'دورك!'; turnEl.textContent = t('game.your_turn');
turnEl.className = 'dg-turn-status'; turnEl.className = 'dg-turn-status';
} else { } else {
turnEl.textContent = 'الخصم يلعب...'; turnEl.textContent = t('game.opponent_plays');
turnEl.className = 'dg-turn-status dg-waiting'; turnEl.className = 'dg-turn-status dg-waiting';
} }
} }
...@@ -1142,7 +1142,7 @@ function updateUI(el) { ...@@ -1142,7 +1142,7 @@ function updateUI(el) {
const oppIdx = 1 - state.myPlayerIndex; const oppIdx = 1 - state.myPlayerIndex;
const oppHandLen = state.hands[oppIdx]?.length || 0; const oppHandLen = state.hands[oppIdx]?.length || 0;
if (oppCountEl) { if (oppCountEl) {
oppCountEl.textContent = `${oppHandLen} قطع`; oppCountEl.textContent = t('common.pieces', { n: oppHandLen });
} }
// Reset tension on new round // Reset tension on new round
...@@ -1162,7 +1162,7 @@ function updateUI(el) { ...@@ -1162,7 +1162,7 @@ function updateUI(el) {
juice.hapticLight(); juice.hapticLight();
const alert = document.createElement('div'); const alert = document.createElement('div');
alert.style.cssText = 'position:absolute;top:56px;left:50%;transform:translateX(-50%);background:rgba(239,68,68,0.9);color:#fff;padding:6px 16px;border-radius:8px;font-size:12px;font-weight:700;z-index:50;'; alert.style.cssText = 'position:absolute;top:56px;left:50%;transform:translateX(-50%);background:rgba(239,68,68,0.9);color:#fff;padding:6px 16px;border-radius:8px;font-size:12px;font-weight:700;z-index:50;';
alert.textContent = oppHandLen === 1 ? '⚠️ قطعة واحدة!' : '⚠️ قطعتين!'; alert.textContent = oppHandLen === 1 ? t('game.one_piece_warning') : t('game.two_piece_warning');
el.querySelector('#domino-wrap')?.appendChild(alert); el.querySelector('#domino-wrap')?.appendChild(alert);
alert.animate([{opacity:0,transform:'translateX(-50%) translateY(-10px)'},{opacity:1,transform:'translateX(-50%) translateY(0)'},{opacity:0,transform:'translateX(-50%) translateY(-10px)'}], {duration:2500}).onfinish = () => alert.remove(); alert.animate([{opacity:0,transform:'translateX(-50%) translateY(-10px)'},{opacity:1,transform:'translateX(-50%) translateY(0)'},{opacity:0,transform:'translateX(-50%) translateY(-10px)'}], {duration:2500}).onfinish = () => alert.remove();
} }
...@@ -1255,11 +1255,11 @@ function refreshHand() { ...@@ -1255,11 +1255,11 @@ function refreshHand() {
async function confirmResign(el) { async function confirmResign(el) {
if (state.gameOver || state.matchOver) return; if (state.gameOver || state.matchOver) return;
const confirmed = await modal.confirm('هل تريد الاستسلام؟', { const confirmed = await modal.confirm(t('game.resign_confirm'), {
title: 'استسلام', title: t('game.resign'),
icon: '🏳️', icon: '🏳️',
confirmText: 'نعم، استسلم', confirmText: t('game.resign_yes'),
cancelText: 'تراجع', cancelText: t('game.resign_no'),
danger: true danger: true
}); });
if (!confirmed) return; if (!confirmed) return;
......
...@@ -13,7 +13,7 @@ export function mountResult(el, params) { ...@@ -13,7 +13,7 @@ export function mountResult(el, params) {
const isDraw = result === 'draw'; const isDraw = result === 'draw';
const icon = isWin ? `${emoji('crown', '👑', 32)}<br>${emoji('trophy', '🏆', 56)}` : isDraw ? emoji('handshake', '🤝', 56) : emoji('skull', '💀', 56); const icon = isWin ? `${emoji('crown', '👑', 32)}<br>${emoji('trophy', '🏆', 56)}` : isDraw ? emoji('handshake', '🤝', 56) : emoji('skull', '💀', 56);
const title = resigned ? 'استسلمت' : reason === 'resign' ? 'الخصم استسلم!' : reason === 'abandon' ? 'الخصم انقطع' : isWin ? 'فوز!' : isDraw ? 'تعادل' : 'خسارة'; const title = resigned ? t('game.resigned') : reason === 'resign' ? t('game.opponent_resigned') : reason === 'abandon' ? t('game.opponent_abandoned') : isWin ? t('game.win') : isDraw ? t('game.draw_game') : t('game.loss');
const titleColor = isWin ? '#4ade80' : isDraw ? '#fbbf24' : '#fca5a5'; const titleColor = isWin ? '#4ade80' : isDraw ? '#fbbf24' : '#fca5a5';
el.innerHTML = ` el.innerHTML = `
...@@ -25,16 +25,16 @@ export function mountResult(el, params) { ...@@ -25,16 +25,16 @@ export function mountResult(el, params) {
<div style="display:flex;gap:20px;align-items:center;animation:fadeSlideUp 0.4s ease 0.3s both;"> <div style="display:flex;gap:20px;align-items:center;animation:fadeSlideUp 0.4s ease 0.3s both;">
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:32px;font-weight:800;color:#4ade80;" id="score-me">0</div> <div style="font-size:32px;font-weight:800;color:#4ade80;" id="score-me">0</div>
<div style="font-size:11px;color:#86efac;">أنت</div> <div style="font-size:11px;color:#86efac;">${t('common.you')}</div>
</div> </div>
<div style="font-size:18px;color:#475569;">—</div> <div style="font-size:18px;color:#475569;">—</div>
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:32px;font-weight:800;color:#fca5a5;" id="score-opp">0</div> <div style="font-size:32px;font-weight:800;color:#fca5a5;" id="score-opp">0</div>
<div style="font-size:11px;color:#fca5a5;">خصم</div> <div style="font-size:11px;color:#fca5a5;">${t('common.opponent')}</div>
</div> </div>
</div> </div>
<div style="font-size:12px;color:#64748b;animation:fadeSlideUp 0.4s ease 0.35s both;">${rounds} ${rounds === 1 ? 'جولة' : 'جولات'}</div> <div style="font-size:12px;color:#64748b;animation:fadeSlideUp 0.4s ease 0.35s both;">${rounds === 1 ? t('game.rounds_count', { n: rounds }) : t('game.rounds_count_plural', { n: rounds })}</div>
<!-- Rewards (populated after server response) --> <!-- Rewards (populated after server response) -->
<div id="rewards-section" style="display:flex;gap:12px;margin-top:8px;animation:fadeSlideUp 0.4s ease 0.5s both;"> <div id="rewards-section" style="display:flex;gap:12px;margin-top:8px;animation:fadeSlideUp 0.4s ease 0.5s both;">
...@@ -49,12 +49,12 @@ export function mountResult(el, params) { ...@@ -49,12 +49,12 @@ export function mountResult(el, params) {
</div> </div>
<!-- Rating change --> <!-- Rating change -->
<div id="rating-section" style="font-size:14px;font-weight:600;color:#94a3b8;animation:fadeSlideUp 0.4s ease 0.6s both;">التصنيف: ...</div> <div id="rating-section" style="font-size:14px;font-weight:600;color:#94a3b8;animation:fadeSlideUp 0.4s ease 0.6s both;">${t('game.rating')}: ...</div>
<!-- Actions --> <!-- Actions -->
<div style="display:flex;gap:10px;margin-top:16px;width:100%;max-width:300px;animation:fadeSlideUp 0.4s ease 0.7s both;"> <div style="display:flex;gap:10px;margin-top:16px;width:100%;max-width:300px;animation:fadeSlideUp 0.4s ease 0.7s both;">
<button class="btn btn-primary" id="btn-rematch" style="flex:1;min-height:48px;border-radius:14px;font-size:15px;font-weight:700;background:linear-gradient(135deg,#10b981,#06b6d4);">إعادة</button> <button class="btn btn-primary" id="btn-rematch" style="flex:1;min-height:48px;border-radius:14px;font-size:15px;font-weight:700;background:linear-gradient(135deg,#10b981,#06b6d4);">${t('result.rematch')}</button>
<button class="btn btn-secondary" id="btn-back" style="flex:1;min-height:48px;border-radius:14px;font-size:15px;font-weight:700;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);color:#e2e8f0;">رجوع</button> <button class="btn btn-secondary" id="btn-back" style="flex:1;min-height:48px;border-radius:14px;font-size:15px;font-weight:700;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);color:#e2e8f0;">${t('common.back')}</button>
</div> </div>
</div> </div>
<style> <style>
...@@ -150,7 +150,7 @@ async function completeOnServer(el, matchId, result, mode) { ...@@ -150,7 +150,7 @@ async function completeOnServer(el, matchId, result, mode) {
if (ratingEl) { if (ratingEl) {
const sign = ratingChange >= 0 ? '+' : ''; const sign = ratingChange >= 0 ? '+' : '';
const color = ratingChange > 0 ? '#4ade80' : ratingChange === 0 ? '#fbbf24' : '#fca5a5'; const color = ratingChange > 0 ? '#4ade80' : ratingChange === 0 ? '#fbbf24' : '#fca5a5';
ratingEl.innerHTML = `التصنيف: <span style="color:${color};font-weight:700;">${sign}${ratingChange}</span>`; ratingEl.innerHTML = `${t('game.rating')}: <span style="color:${color};font-weight:700;">${sign}${ratingChange}</span>`;
} }
bus.emit('coins:earned', { amount: coins }); bus.emit('coins:earned', { amount: coins });
......
...@@ -25,26 +25,26 @@ function renderMenu(el) { ...@@ -25,26 +25,26 @@ function renderMenu(el) {
<div class="domino-menu"> <div class="domino-menu">
<div class="dm-hero"> <div class="dm-hero">
<div class="dm-icon">${emoji('domino_tile', '🁣', 56)}</div> <div class="dm-icon">${emoji('domino_tile', '🁣', 56)}</div>
<h1 class="dm-title">دومينو</h1> <h1 class="dm-title">${t('domino.title')}</h1>
<p class="dm-subtitle">أول من يوصل الهدف يفوز!</p> <p class="dm-subtitle">${t('domino.subtitle')}</p>
</div> </div>
<div class="dm-buttons"> <div class="dm-buttons">
<button class="dm-btn dm-btn-primary" id="btn-bot"> <button class="dm-btn dm-btn-primary" id="btn-bot">
<span class="dm-btn-icon">${emoji('robot', '🤖', 22)}</span> <span class="dm-btn-icon">${emoji('robot', '🤖', 22)}</span>
<span class="dm-btn-label">ضد البوت</span> <span class="dm-btn-label">${t('game.vs_bot')}</span>
</button> </button>
<button class="dm-btn dm-btn-online" id="btn-online"> <button class="dm-btn dm-btn-online" id="btn-online">
<span class="dm-btn-icon">${emoji('globe', '🌍', 22)}</span> <span class="dm-btn-icon">${emoji('globe', '🌍', 22)}</span>
<span class="dm-btn-label">أونلاين</span> <span class="dm-btn-label">${t('game.online')}</span>
</button> </button>
<button class="dm-btn dm-btn-friend" id="btn-friend"> <button class="dm-btn dm-btn-friend" id="btn-friend">
<span class="dm-btn-icon">${emoji('handshake', '🤝', 20)}</span> <span class="dm-btn-icon">${emoji('handshake', '🤝', 20)}</span>
<span class="dm-btn-label">تحدي صديق</span> <span class="dm-btn-label">${t('game.vs_friend')}</span>
</button> </button>
</div> </div>
<button class="dm-back" id="btn-back">رجوع</button> <button class="dm-back" id="btn-back">${t('common.back')}</button>
</div> </div>
<style> <style>
.domino-menu { .domino-menu {
...@@ -98,16 +98,16 @@ function renderMenu(el) { ...@@ -98,16 +98,16 @@ function renderMenu(el) {
function renderBotPicker(el) { function renderBotPicker(el) {
const levels = [ const levels = [
{ key: 'beginner', label: 'مبتدئ', desc: 'يلعب عشوائي', icon: '😊', color: '#4ade80', bg: 'rgba(74,222,128,0.08)', border: 'rgba(74,222,128,0.2)' }, { key: 'beginner', label: t('domino.beginner'), desc: t('domino.beginner_desc'), icon: '😊', color: '#4ade80', bg: 'rgba(74,222,128,0.08)', border: 'rgba(74,222,128,0.2)' },
{ key: 'intermediate', label: 'متوسط', desc: 'يفضل النقاط العالية', icon: '🧐', color: '#fbbf24', bg: 'rgba(251,191,36,0.08)', border: 'rgba(251,191,36,0.2)' }, { key: 'intermediate', label: t('domino.intermediate'), desc: t('domino.intermediate_desc'), icon: '🧐', color: '#fbbf24', bg: 'rgba(251,191,36,0.08)', border: 'rgba(251,191,36,0.2)' },
{ key: 'expert', label: 'خبير', desc: 'استراتيجي ومخادع', icon: '🧠', color: '#f87171', bg: 'rgba(248,113,113,0.08)', border: 'rgba(248,113,113,0.2)' } { key: 'expert', label: t('domino.expert'), desc: t('domino.expert_desc'), icon: '🧠', color: '#f87171', bg: 'rgba(248,113,113,0.08)', border: 'rgba(248,113,113,0.2)' }
]; ];
el.innerHTML = ` el.innerHTML = `
<div class="domino-menu"> <div class="domino-menu">
<div class="dm-hero"> <div class="dm-hero">
<h2 style="font-size:20px;font-weight:700;color:#f8fafc;margin:0;">اختر مستوى البوت</h2> <h2 style="font-size:20px;font-weight:700;color:#f8fafc;margin:0;">${t('domino.select_bot')}</h2>
<p style="font-size:12px;color:#94a3b8;margin:8px 0 0;">كل مستوى له استراتيجية مختلفة</p> <p style="font-size:12px;color:#94a3b8;margin:8px 0 0;">${t('domino.bot_strategy')}</p>
</div> </div>
<div class="dm-buttons"> <div class="dm-buttons">
${levels.map(l => ` ${levels.map(l => `
...@@ -121,7 +121,7 @@ function renderBotPicker(el) { ...@@ -121,7 +121,7 @@ function renderBotPicker(el) {
</button> </button>
`).join('')} `).join('')}
</div> </div>
<button class="dm-back" id="btn-back-bot">رجوع</button> <button class="dm-back" id="btn-back-bot">${t('common.back')}</button>
</div> </div>
<style> <style>
.dm-level-btn { .dm-level-btn {
...@@ -150,16 +150,16 @@ function renderBotPicker(el) { ...@@ -150,16 +150,16 @@ function renderBotPicker(el) {
function renderTargetPicker(el, botLevel) { function renderTargetPicker(el, botLevel) {
const targets = [ const targets = [
{ value: 50, label: '50 نقطة', desc: 'مباراة سريعة (~5 دقائق)', icon: '⚡', color: '#4ade80', bg: 'rgba(74,222,128,0.08)', border: 'rgba(74,222,128,0.2)' }, { value: 50, label: t('domino.50_points'), desc: t('domino.50_desc'), icon: '⚡', color: '#4ade80', bg: 'rgba(74,222,128,0.08)', border: 'rgba(74,222,128,0.2)' },
{ value: 100, label: '100 نقطة', desc: 'كلاسيك (~10 دقائق)', icon: '🎯', color: '#fbbf24', bg: 'rgba(251,191,36,0.08)', border: 'rgba(251,191,36,0.2)' }, { value: 100, label: t('domino.100_points'), desc: t('domino.100_desc'), icon: '🎯', color: '#fbbf24', bg: 'rgba(251,191,36,0.08)', border: 'rgba(251,191,36,0.2)' },
{ value: 150, label: '150 نقطة', desc: 'مباراة طويلة (~15 دقيقة)', icon: '🔥', color: '#f87171', bg: 'rgba(248,113,113,0.08)', border: 'rgba(248,113,113,0.2)' } { value: 150, label: t('domino.150_points'), desc: t('domino.150_desc'), icon: '🔥', color: '#f87171', bg: 'rgba(248,113,113,0.08)', border: 'rgba(248,113,113,0.2)' }
]; ];
el.innerHTML = ` el.innerHTML = `
<div class="domino-menu"> <div class="domino-menu">
<div class="dm-hero"> <div class="dm-hero">
<h2 style="font-size:20px;font-weight:700;color:#f8fafc;margin:0;">اختر الهدف</h2> <h2 style="font-size:20px;font-weight:700;color:#f8fafc;margin:0;">${t('domino.select_target')}</h2>
<p style="font-size:12px;color:#94a3b8;margin:8px 0 0;">أول من يوصل النقاط يفوز بالمباراة</p> <p style="font-size:12px;color:#94a3b8;margin:8px 0 0;">${t('domino.target_hint')}</p>
</div> </div>
<div class="dm-buttons"> <div class="dm-buttons">
${targets.map(t => ` ${targets.map(t => `
...@@ -173,7 +173,7 @@ function renderTargetPicker(el, botLevel) { ...@@ -173,7 +173,7 @@ function renderTargetPicker(el, botLevel) {
</button> </button>
`).join('')} `).join('')}
</div> </div>
<button class="dm-back" id="btn-back-target">رجوع</button> <button class="dm-back" id="btn-back-target">${t('common.back')}</button>
</div> </div>
`; `;
...@@ -199,7 +199,7 @@ function renderLobby(el, { challengeId, friendId, friendName }) { ...@@ -199,7 +199,7 @@ function renderLobby(el, { challengeId, friendId, friendName }) {
<div class="dm-hero"> <div class="dm-hero">
<div style="font-size:44px;margin-bottom:12px;">${emoji('domino_tile', '🁣', 44)}</div> <div style="font-size:44px;margin-bottom:12px;">${emoji('domino_tile', '🁣', 44)}</div>
<h2 style="font-size:18px;font-weight:700;color:#f8fafc;margin:0;"> <h2 style="font-size:18px;font-weight:700;color:#f8fafc;margin:0;">
${isHost ? 'بانتظار الصديق...' : `تحدي من ${friendName || 'صديق'}`} ${isHost ? t('room.waiting_friend') : t('room.challenge_from', { name: friendName || t('common.friend') })}
</h2> </h2>
</div> </div>
...@@ -207,12 +207,12 @@ function renderLobby(el, { challengeId, friendId, friendName }) { ...@@ -207,12 +207,12 @@ function renderLobby(el, { challengeId, friendId, friendName }) {
<div style="width:64px;height:64px;border-radius:50%;border:3px solid rgba(228,172,56,0.4);display:flex;align-items:center;justify-content:center;animation:dmPulseRing 2s ease-in-out infinite;"> <div style="width:64px;height:64px;border-radius:50%;border:3px solid rgba(228,172,56,0.4);display:flex;align-items:center;justify-content:center;animation:dmPulseRing 2s ease-in-out infinite;">
${emoji('clock', '⏳', 26)} ${emoji('clock', '⏳', 26)}
</div> </div>
<div style="font-size:13px;color:#E4AC38;" id="lobby-msg">${isHost ? 'أرسل الدعوة لصديقك' : 'اضغط قبول للبدء'}</div> <div style="font-size:13px;color:#E4AC38;" id="lobby-msg">${isHost ? t('room.send_invite') : t('room.tap_accept')}</div>
</div> </div>
<div style="display:flex;gap:10px;margin-top:20px;"> <div style="display:flex;gap:10px;margin-top:20px;">
${!isHost ? `<button class="dm-btn dm-btn-online" id="btn-accept" style="min-height:48px;padding:0 28px;font-size:15px;">قبول</button>` : ''} ${!isHost ? `<button class="dm-btn dm-btn-online" id="btn-accept" style="min-height:48px;padding:0 28px;font-size:15px;">${t('social.accept')}</button>` : ''}
<button class="dm-btn dm-btn-friend" id="btn-cancel-lobby" style="min-height:48px;padding:0 20px;font-size:14px;border-color:rgba(239,68,68,0.3);color:#fca5a5;">إلغاء</button> <button class="dm-btn dm-btn-friend" id="btn-cancel-lobby" style="min-height:48px;padding:0 20px;font-size:14px;border-color:rgba(239,68,68,0.3);color:#fca5a5;">${t('common.cancel')}</button>
</div> </div>
</div> </div>
<style> <style>
...@@ -246,7 +246,7 @@ async function pollForAcceptance(el, friendId) { ...@@ -246,7 +246,7 @@ async function pollForAcceptance(el, friendId) {
if (data?.id) { if (data?.id) {
const matchId = data.id; const matchId = data.id;
const msgEl = el.querySelector('#lobby-msg'); const msgEl = el.querySelector('#lobby-msg');
if (msgEl) msgEl.textContent = 'تم إنشاء المباراة، بانتظار القبول...'; if (msgEl) msgEl.textContent = t('room.match_created');
pollTimer = setInterval(async () => { pollTimer = setInterval(async () => {
try { try {
......
This diff is collapsed.
...@@ -6,7 +6,7 @@ import { t } from '../../../core/i18n.js'; ...@@ -6,7 +6,7 @@ import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
const PLACE_EMOJI = ['🥇', '🥈', '🥉', '4️⃣']; const PLACE_EMOJI = ['🥇', '🥈', '🥉', '4️⃣'];
const PLACE_LABEL = ['الأول', 'الثاني', 'الثالث', 'الرابع']; const PLACE_LABEL = [t('ludo.place_first'), t('ludo.place_second'), t('ludo.place_third'), t('ludo.place_fourth')];
const PLACE_COLOR = ['#FFD700', '#C0C0C0', '#CD7F32', '#64748b']; const PLACE_COLOR = ['#FFD700', '#C0C0C0', '#CD7F32', '#64748b'];
export function mountResult(el, params) { export function mountResult(el, params) {
...@@ -25,7 +25,7 @@ export function mountResult(el, params) { ...@@ -25,7 +25,7 @@ export function mountResult(el, params) {
const pIdx = winners[rank]; const pIdx = winners[rank];
leaderboard.push({ leaderboard.push({
rank: rank + 1, rank: rank + 1,
name: playerNames[pIdx] || `لاعب ${pIdx + 1}`, name: playerNames[pIdx] || `${t('common.player')} ${pIdx + 1}`,
color: playerColors[pIdx] || '#94a3b8', color: playerColors[pIdx] || '#94a3b8',
isMe: pIdx === 0, isMe: pIdx === 0,
}); });
...@@ -35,7 +35,7 @@ export function mountResult(el, params) { ...@@ -35,7 +35,7 @@ export function mountResult(el, params) {
if (!winners.includes(i)) { if (!winners.includes(i)) {
leaderboard.push({ leaderboard.push({
rank: leaderboard.length + 1, rank: leaderboard.length + 1,
name: playerNames[i] || `لاعب ${i + 1}`, name: playerNames[i] || `${t('common.player')} ${i + 1}`,
color: playerColors[i] || '#94a3b8', color: playerColors[i] || '#94a3b8',
isMe: i === 0, isMe: i === 0,
}); });
...@@ -50,11 +50,11 @@ export function mountResult(el, params) { ...@@ -50,11 +50,11 @@ export function mountResult(el, params) {
: place === 3 ? emoji('medal_3', '🥉', 64) : place === 3 ? emoji('medal_3', '🥉', 64)
: emoji('skull', '💀', 64); : emoji('skull', '💀', 64);
const heroTitle = resigned ? 'انسحبت من المباراة' const heroTitle = resigned ? t('game.withdrew')
: place === 1 ? 'مبروك! أنت البطل' : place === 1 ? t('game.congrats')
: place === 2 ? 'المركز الثاني — أحسنت!' : place === 2 ? t('game.second_place')
: place === 3 ? 'المركز الثالث' : place === 3 ? t('game.third_place')
: 'المركز الرابع'; : t('game.fourth_place');
const heroColor = resigned ? '#EF4444' const heroColor = resigned ? '#EF4444'
: place === 1 ? '#FFD700' : place === 1 ? '#FFD700'
...@@ -73,14 +73,14 @@ export function mountResult(el, params) { ...@@ -73,14 +73,14 @@ export function mountResult(el, params) {
<!-- Leaderboard --> <!-- Leaderboard -->
${leaderboard.length > 0 ? ` ${leaderboard.length > 0 ? `
<div class="lr-leaderboard"> <div class="lr-leaderboard">
<div class="lr-lb-title">ترتيب اللاعبين</div> <div class="lr-lb-title">${t('game.player_ranking')}</div>
${leaderboard.map(p => ` ${leaderboard.map(p => `
<div class="lr-lb-row ${p.isMe ? 'lr-lb-me' : ''}" style="--pc:${p.color};"> <div class="lr-lb-row ${p.isMe ? 'lr-lb-me' : ''}" style="--pc:${p.color};">
<div class="lr-lb-rank" style="color:${PLACE_COLOR[p.rank - 1] || '#64748b'};"> <div class="lr-lb-rank" style="color:${PLACE_COLOR[p.rank - 1] || '#64748b'};">
${p.rank <= 3 ? PLACE_EMOJI[p.rank - 1] : p.rank} ${p.rank <= 3 ? PLACE_EMOJI[p.rank - 1] : p.rank}
</div> </div>
<div class="lr-lb-color" style="background:${p.color};"></div> <div class="lr-lb-color" style="background:${p.color};"></div>
<div class="lr-lb-name">${p.name}${p.isMe ? ' (أنت)' : ''}</div> <div class="lr-lb-name">${p.name}${p.isMe ? ` (${t('common.you')})` : ''}</div>
<div class="lr-lb-place">${PLACE_LABEL[p.rank - 1] || ''}</div> <div class="lr-lb-place">${PLACE_LABEL[p.rank - 1] || ''}</div>
</div> </div>
`).join('')} `).join('')}
......
...@@ -22,29 +22,29 @@ function renderMenu(el) { ...@@ -22,29 +22,29 @@ function renderMenu(el) {
<div class="lr-wrap"> <div class="lr-wrap">
<div class="lr-hero"> <div class="lr-hero">
<div class="lr-icon">${emoji('dice', '🎲', 56)}</div> <div class="lr-icon">${emoji('dice', '🎲', 56)}</div>
<h1 class="lr-title">لودو</h1> <h1 class="lr-title">${t('ludo.title')}</h1>
<p class="lr-subtitle">أول من يوصّل كل قطعه يفوز!</p> <p class="lr-subtitle">${t('ludo.subtitle')}</p>
</div> </div>
<div class="lr-buttons"> <div class="lr-buttons">
<button class="lr-btn lr-btn-primary" id="btn-local"> <button class="lr-btn lr-btn-primary" id="btn-local">
<span class="lr-btn-icon">${emoji('gamepad', '🎮', 22)}</span> <span class="lr-btn-icon">${emoji('gamepad', '🎮', 22)}</span>
<span class="lr-btn-label">لعب محلي</span> <span class="lr-btn-label">${t('ludo.local_play')}</span>
<span class="lr-btn-desc">اختر عدد اللاعبين والبوتات</span> <span class="lr-btn-desc">${t('ludo.local_desc')}</span>
</button> </button>
<button class="lr-btn lr-btn-online" id="btn-online"> <button class="lr-btn lr-btn-online" id="btn-online">
<span class="lr-btn-icon">${emoji('globe', '🌍', 22)}</span> <span class="lr-btn-icon">${emoji('globe', '🌍', 22)}</span>
<span class="lr-btn-label">أونلاين</span> <span class="lr-btn-label">${t('game.online')}</span>
<span class="lr-btn-desc">العب ضد لاعبين حقيقيين</span> <span class="lr-btn-desc">${t('ludo.online_desc')}</span>
</button> </button>
<button class="lr-btn lr-btn-friend" id="btn-friend"> <button class="lr-btn lr-btn-friend" id="btn-friend">
<span class="lr-btn-icon">${emoji('handshake', '🤝', 20)}</span> <span class="lr-btn-icon">${emoji('handshake', '🤝', 20)}</span>
<span class="lr-btn-label">تحدي صديق</span> <span class="lr-btn-label">${t('game.vs_friend')}</span>
<span class="lr-btn-desc">ادعُ أصدقاءك للعب</span> <span class="lr-btn-desc">${t('ludo.friend_desc')}</span>
</button> </button>
</div> </div>
<button class="lr-back" id="btn-back">رجوع</button> <button class="lr-back" id="btn-back">${t('common.back')}</button>
</div> </div>
${getStyles()} ${getStyles()}
`; `;
...@@ -57,7 +57,7 @@ function renderMenu(el) { ...@@ -57,7 +57,7 @@ function renderMenu(el) {
el.querySelector('#btn-online').addEventListener('click', () => { el.querySelector('#btn-online').addEventListener('click', () => {
audio.play('click'); audio.play('click');
if (store.get('auth.isGuest')) { if (store.get('auth.isGuest')) {
bus.emit('toast', { text: 'سجّل دخولك للعب أونلاين' }); bus.emit('toast', { text: t('play.login_required_online') });
return; return;
} }
scene.replace('ludo-room', { mode: 'setup', type: 'online' }); scene.replace('ludo-room', { mode: 'setup', type: 'online' });
...@@ -66,7 +66,7 @@ function renderMenu(el) { ...@@ -66,7 +66,7 @@ function renderMenu(el) {
el.querySelector('#btn-friend').addEventListener('click', () => { el.querySelector('#btn-friend').addEventListener('click', () => {
audio.play('click'); audio.play('click');
if (store.get('auth.isGuest')) { if (store.get('auth.isGuest')) {
bus.emit('toast', { text: 'سجّل دخولك لتحدي صديق' }); bus.emit('toast', { text: t('play.login_required_friend') });
return; return;
} }
scene.push('challenge-friend', { game: 'ludo' }); scene.push('challenge-friend', { game: 'ludo' });
...@@ -85,25 +85,25 @@ function renderSetup(el, params) { ...@@ -85,25 +85,25 @@ function renderSetup(el, params) {
el.innerHTML = ` el.innerHTML = `
<div class="lr-wrap"> <div class="lr-wrap">
<div class="lr-hero" style="margin-bottom:16px;"> <div class="lr-hero" style="margin-bottom:16px;">
<h2 class="lr-title" style="font-size:20px;">${isOnline ? 'إعدادات الأونلاين' : 'إعدادات اللعب'}</h2> <h2 class="lr-title" style="font-size:20px;">${isOnline ? t('ludo.settings_online') : t('ludo.settings_play')}</h2>
<p class="lr-subtitle">${isOnline ? 'اختر عدد اللاعبين الحقيقيين' : 'اختر تشكيلة اللاعبين'}</p> <p class="lr-subtitle">${isOnline ? t('ludo.settings_online_hint') : t('ludo.settings_play_hint')}</p>
</div> </div>
<!-- Player Count --> <!-- Player Count -->
<div class="lr-section"> <div class="lr-section">
<div class="lr-section-title">عدد اللاعبين</div> <div class="lr-section-title">${t('ludo.player_count')}</div>
<div class="lr-grid" id="player-count-grid"> <div class="lr-grid" id="player-count-grid">
<button class="lr-chip lr-chip-active" data-count="4"> <button class="lr-chip lr-chip-active" data-count="4">
<span class="lr-chip-num">4</span> <span class="lr-chip-num">4</span>
<span class="lr-chip-label">كلاسيك</span> <span class="lr-chip-label">${t('ludo.classic')}</span>
</button> </button>
<button class="lr-chip" data-count="3"> <button class="lr-chip" data-count="3">
<span class="lr-chip-num">3</span> <span class="lr-chip-num">3</span>
<span class="lr-chip-label">ثلاثي</span> <span class="lr-chip-label">${t('ludo.triple')}</span>
</button> </button>
<button class="lr-chip" data-count="2"> <button class="lr-chip" data-count="2">
<span class="lr-chip-num">2</span> <span class="lr-chip-num">2</span>
<span class="lr-chip-label">مبارزة</span> <span class="lr-chip-label">${t('ludo.duel')}</span>
</button> </button>
</div> </div>
</div> </div>
...@@ -111,7 +111,7 @@ function renderSetup(el, params) { ...@@ -111,7 +111,7 @@ function renderSetup(el, params) {
<!-- Bot/Human Distribution (local only) --> <!-- Bot/Human Distribution (local only) -->
${!isOnline ? ` ${!isOnline ? `
<div class="lr-section" id="distribution-section"> <div class="lr-section" id="distribution-section">
<div class="lr-section-title">توزيعة اللاعبين</div> <div class="lr-section-title">${t('ludo.player_layout')}</div>
<div id="dist-options"></div> <div id="dist-options"></div>
</div> </div>
` : ''} ` : ''}
...@@ -119,19 +119,19 @@ function renderSetup(el, params) { ...@@ -119,19 +119,19 @@ function renderSetup(el, params) {
${!isOnline ? ` ${!isOnline ? `
<!-- Bot Difficulty --> <!-- Bot Difficulty -->
<div class="lr-section" id="difficulty-section"> <div class="lr-section" id="difficulty-section">
<div class="lr-section-title">مستوى البوت</div> <div class="lr-section-title">${t('ludo.bot_level')}</div>
<div class="lr-grid" id="difficulty-grid"> <div class="lr-grid" id="difficulty-grid">
<button class="lr-chip" data-diff="easy"> <button class="lr-chip" data-diff="easy">
<span>😊</span> <span>😊</span>
<span class="lr-chip-label">سهل</span> <span class="lr-chip-label">${t('ludo.easy')}</span>
</button> </button>
<button class="lr-chip lr-chip-active" data-diff="medium"> <button class="lr-chip lr-chip-active" data-diff="medium">
<span>🧐</span> <span>🧐</span>
<span class="lr-chip-label">متوسط</span> <span class="lr-chip-label">${t('ludo.medium')}</span>
</button> </button>
<button class="lr-chip" data-diff="hard"> <button class="lr-chip" data-diff="hard">
<span>🧠</span> <span>🧠</span>
<span class="lr-chip-label">صعب</span> <span class="lr-chip-label">${t('ludo.hard')}</span>
</button> </button>
</div> </div>
</div> </div>
...@@ -139,16 +139,16 @@ function renderSetup(el, params) { ...@@ -139,16 +139,16 @@ function renderSetup(el, params) {
<!-- Seating Preview --> <!-- Seating Preview -->
<div class="lr-section"> <div class="lr-section">
<div class="lr-section-title">ترتيب المقاعد</div> <div class="lr-section-title">${t('ludo.seat_order')}</div>
<div id="seat-preview" class="lr-seat-preview"></div> <div id="seat-preview" class="lr-seat-preview"></div>
</div> </div>
<!-- Start Button --> <!-- Start Button -->
<button class="lr-btn lr-btn-start" id="btn-start"> <button class="lr-btn lr-btn-start" id="btn-start">
${isOnline ? 'ابحث عن مباراة' : 'ابدأ اللعب'} ${isOnline ? t('ludo.search_match') : t('ludo.start_play')}
</button> </button>
<button class="lr-back" id="btn-back-setup">رجوع</button> <button class="lr-back" id="btn-back-setup">${t('common.back')}</button>
</div> </div>
${getStyles()} ${getStyles()}
`; `;
...@@ -189,10 +189,10 @@ function renderSetup(el, params) { ...@@ -189,10 +189,10 @@ function renderSetup(el, params) {
for (let h = 1; h <= playerCount; h++) { for (let h = 1; h <= playerCount; h++) {
const bots = playerCount - h; const bots = playerCount - h;
const label = h === playerCount const label = h === playerCount
? `${h} لاعبين (بدون بوت)` ? t('ludo.players_no_bot', { n: h })
: h === 1 : h === 1
? `لاعب واحد + ${bots} بوت` ? t('ludo.one_player_bots', { n: bots })
: `${h} لاعبين + ${bots} بوت`; : t('ludo.players_bots', { h, n: bots });
options.push({ humans: h, bots, label }); options.push({ humans: h, bots, label });
} }
...@@ -225,7 +225,7 @@ function renderSetup(el, params) { ...@@ -225,7 +225,7 @@ function renderSetup(el, params) {
const seats = getSeatPositions(playerCount); const seats = getSeatPositions(playerCount);
const colors = ['#E53935', '#43A047', '#FDD835', '#1E88E5']; const colors = ['#E53935', '#43A047', '#FDD835', '#1E88E5'];
const labels = ['أحمر', 'أخضر', 'أصفر', 'أزرق']; const labels = [t('ludo.red'), t('ludo.green'), t('ludo.yellow'), t('ludo.blue')];
const positions = ['bottom-left', 'top-left', 'top-right', 'bottom-right']; const positions = ['bottom-left', 'top-left', 'top-right', 'bottom-right'];
preview.innerHTML = ` preview.innerHTML = `
...@@ -290,10 +290,10 @@ function renderSearching(el, params) { ...@@ -290,10 +290,10 @@ function renderSearching(el, params) {
<div class="lr-pulse-ring"> <div class="lr-pulse-ring">
<div class="lr-pulse-inner">${emoji('dice', '🎲', 32)}</div> <div class="lr-pulse-inner">${emoji('dice', '🎲', 32)}</div>
</div> </div>
<h2 class="lr-title" style="font-size:18px;margin-top:20px;">جاري البحث...</h2> <h2 class="lr-title" style="font-size:18px;margin-top:20px;">${t('ludo.searching')}</h2>
<p class="lr-subtitle">بنوصّلك بلاعبين قريب</p> <p class="lr-subtitle">${t('ludo.searching_hint')}</p>
</div> </div>
<button class="lr-btn lr-btn-friend" id="btn-cancel" style="max-width:200px;">إلغاء</button> <button class="lr-btn lr-btn-friend" id="btn-cancel" style="max-width:200px;">${t('common.cancel')}</button>
</div> </div>
${getStyles()} ${getStyles()}
`; `;
......
...@@ -7,7 +7,7 @@ import { emoji } from '../../../core/theme.js'; ...@@ -7,7 +7,7 @@ import { emoji } from '../../../core/theme.js';
export async function mountBrowser(el) { export async function mountBrowser(el) {
el.innerHTML = ` el.innerHTML = `
<div style="padding:var(--s-4);display:flex;flex-direction:column;gap:var(--s-4);"> <div style="padding:var(--s-4);display:flex;flex-direction:column;gap:var(--s-4);">
<h2 style="font-size:20px;font-weight:700;">المنظمات</h2> <h2 style="font-size:20px;font-weight:700;">${t('org.title')}</h2>
<div id="org-list"> <div id="org-list">
<div class="skeleton" style="height:80px;margin-bottom:var(--s-3);"></div> <div class="skeleton" style="height:80px;margin-bottom:var(--s-3);"></div>
<div class="skeleton" style="height:80px;"></div> <div class="skeleton" style="height:80px;"></div>
...@@ -37,9 +37,9 @@ function renderOrgs(el, orgs) { ...@@ -37,9 +37,9 @@ function renderOrgs(el, orgs) {
</div> </div>
<div style="flex:1;"> <div style="flex:1;">
<div style="font-size:14px;font-weight:600;">${org.name_ar || org.name}</div> <div style="font-size:14px;font-weight:600;">${org.name_ar || org.name}</div>
<div style="font-size:11px;color:var(--text-secondary);">${emoji('people', '👥', 11)} ${org.member_count || 0} عضو</div> <div style="font-size:11px;color:var(--text-secondary);">${emoji('people', '👥', 11)} ${org.member_count || 0} ${t('profile.member')}</div>
</div> </div>
<button class="btn btn-secondary" style="font-size:11px;min-height:32px;padding:var(--s-1) var(--s-3);">انضم</button> <button class="btn btn-secondary" style="font-size:11px;min-height:32px;padding:var(--s-1) var(--s-3);">${t('org.join_btn')}</button>
</div> </div>
`).join(''); `).join('');
......
...@@ -43,18 +43,18 @@ function renderOrg(el, org) { ...@@ -43,18 +43,18 @@ function renderOrg(el, org) {
<div style="display:flex;gap:var(--s-4);justify-content:center;margin-top:var(--s-4);"> <div style="display:flex;gap:var(--s-4);justify-content:center;margin-top:var(--s-4);">
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:18px;font-weight:700;color:var(--gold);">${org.member_count || 0}</div> <div style="font-size:18px;font-weight:700;color:var(--gold);">${org.member_count || 0}</div>
<div style="font-size:11px;color:var(--text-secondary);">أعضاء</div> <div style="font-size:11px;color:var(--text-secondary);">${t('org.members')}</div>
</div> </div>
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:18px;font-weight:700;color:var(--cyan);">${org.game_focus || 'chess'}</div> <div style="font-size:18px;font-weight:700;color:var(--cyan);">${org.game_focus || 'chess'}</div>
<div style="font-size:11px;color:var(--text-secondary);">اللعبة</div> <div style="font-size:11px;color:var(--text-secondary);">${t('org.game')}</div>
</div> </div>
</div> </div>
</div> </div>
${org.members && org.members.length > 0 ? ` ${org.members && org.members.length > 0 ? `
<div class="card" style="padding:var(--s-4);"> <div class="card" style="padding:var(--s-4);">
<div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">الأعضاء (${org.members.length})</div> <div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${t('org.members_section')} (${org.members.length})</div>
${org.members.slice(0, 10).map(m => ` ${org.members.slice(0, 10).map(m => `
<div style="display:flex;align-items:center;gap:var(--s-2);padding:var(--s-1) 0;"> <div style="display:flex;align-items:center;gap:var(--s-2);padding:var(--s-1) 0;">
<div style="width:24px;height:24px;border-radius:50%;background:var(--bg-elevated);display:flex;align-items:center;justify-content:center;font-size:12px;">${emoji('person', '👤', 12)}</div> <div style="width:24px;height:24px;border-radius:50%;background:var(--bg-elevated);display:flex;align-items:center;justify-content:center;font-size:12px;">${emoji('person', '👤', 12)}</div>
...@@ -65,14 +65,14 @@ function renderOrg(el, org) { ...@@ -65,14 +65,14 @@ function renderOrg(el, org) {
</div> </div>
` : ''} ` : ''}
<button class="btn btn-primary w-full" id="join-btn">انضم للمنظمة</button> <button class="btn btn-primary w-full" id="join-btn">${t('org.join')}</button>
`; `;
el.querySelector('#join-btn')?.addEventListener('click', async () => { el.querySelector('#join-btn')?.addEventListener('click', async () => {
try { try {
await net.post('orgs.php', { action: 'join', org_id: org.id }); await net.post('orgs.php', { action: 'join', org_id: org.id });
audio.play('coin', 'reward'); audio.play('coin', 'reward');
el.querySelector('#join-btn').textContent = '✅ تم الانضمام'; el.querySelector('#join-btn').textContent = `✅ ${t('org.joined')}`;
el.querySelector('#join-btn').disabled = true; el.querySelector('#join-btn').disabled = true;
} catch (e) { } catch (e) {
audio.play('click'); audio.play('click');
......
This diff is collapsed.
...@@ -3,6 +3,7 @@ import * as scene from '../../../core/scene.js'; ...@@ -3,6 +3,7 @@ import * as scene from '../../../core/scene.js';
import * as audio from '../../../core/audio.js'; import * as audio from '../../../core/audio.js';
import * as store from '../../../core/store.js'; import * as store from '../../../core/store.js';
import * as juice from '../../../core/juice.js'; import * as juice from '../../../core/juice.js';
import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
let pollTimer = null; let pollTimer = null;
...@@ -22,13 +23,13 @@ export function mountLobby(el, params = {}) { ...@@ -22,13 +23,13 @@ export function mountLobby(el, params = {}) {
if (pollTimer) clearInterval(pollTimer); if (pollTimer) clearInterval(pollTimer);
const myName = store.get('player.display_name') || store.get('player.username') || 'أنت'; const myName = store.get('player.display_name') || store.get('player.username') || t('common.you');
const myAvatar = store.get('player.avatar_url'); const myAvatar = store.get('player.avatar_url');
const friendName = friendProfile?.display_name || friendProfile?.username || 'الخصم'; const friendName = friendProfile?.display_name || friendProfile?.username || t('common.opponent');
const friendAvatar = friendProfile?.avatar_url; const friendAvatar = friendProfile?.avatar_url;
const tcLabel = formatTimeControl(timeControl); const tcLabel = formatTimeControl(timeControl);
const gameLabel = gameKey === 'ludo' ? 'لودو' : gameKey === 'domino' ? 'دومينو' : 'شطرنج'; const gameLabel = gameKey === 'ludo' ? t('game.ludo') : gameKey === 'domino' ? t('game.domino') : t('game.chess');
const gameIcon = gameKey === 'ludo' ? '🎲' : gameKey === 'domino' ? '🁣' : '♟'; const gameIcon = gameKey === 'ludo' ? '🎲' : gameKey === 'domino' ? '🁣' : '♟';
el.innerHTML = ` el.innerHTML = `
...@@ -36,7 +37,7 @@ export function mountLobby(el, params = {}) { ...@@ -36,7 +37,7 @@ export function mountLobby(el, params = {}) {
<!-- Header --> <!-- Header -->
<div class="lobby-header"> <div class="lobby-header">
<button id="lobby-back" class="lobby-back-btn">←</button> <button id="lobby-back" class="lobby-back-btn">←</button>
<div class="lobby-title">${emoji('challenge_swords', '⚔️', 18)} غرفة التحدي</div> <div class="lobby-title">${emoji('challenge_swords', '⚔️', 18)} ${t('lobby.title')}</div>
</div> </div>
<!-- Match Info --> <!-- Match Info -->
...@@ -56,7 +57,7 @@ export function mountLobby(el, params = {}) { ...@@ -56,7 +57,7 @@ export function mountLobby(el, params = {}) {
${myAvatar ? `<img src="${myAvatar}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` : emoji('person', '👤', 28)} ${myAvatar ? `<img src="${myAvatar}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` : emoji('person', '👤', 28)}
</div> </div>
<div class="lobby-player-name">${myName}</div> <div class="lobby-player-name">${myName}</div>
<div class="lobby-player-status ready">${emoji('check', '✓', 11)} جاهز</div> <div class="lobby-player-status ready">${emoji('check', '✓', 11)} ${t('lobby.ready')}</div>
${color ? `<div class="lobby-color" style="background:${color === 'w' ? '#fff' : '#1a1a1a'};border:2px solid ${color === 'w' ? '#e2e8f0' : '#475569'};width:20px;height:20px;border-radius:50%;margin-top:6px;"></div>` : ''} ${color ? `<div class="lobby-color" style="background:${color === 'w' ? '#fff' : '#1a1a1a'};border:2px solid ${color === 'w' ? '#e2e8f0' : '#475569'};width:20px;height:20px;border-radius:50%;margin-top:6px;"></div>` : ''}
</div> </div>
...@@ -70,21 +71,21 @@ export function mountLobby(el, params = {}) { ...@@ -70,21 +71,21 @@ export function mountLobby(el, params = {}) {
<div class="lobby-avatar opponent" id="lobby-opponent-avatar"> <div class="lobby-avatar opponent" id="lobby-opponent-avatar">
${friendAvatar ? `<img src="${friendAvatar}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` : `<div class="lobby-waiting-pulse">${emoji('hourglass', '⏳', 28)}</div>`} ${friendAvatar ? `<img src="${friendAvatar}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` : `<div class="lobby-waiting-pulse">${emoji('hourglass', '⏳', 28)}</div>`}
</div> </div>
<div class="lobby-player-name" id="lobby-opponent-name">${isHost ? (friendName || 'في الانتظار...') : friendName}</div> <div class="lobby-player-name" id="lobby-opponent-name">${isHost ? (friendName || t('lobby.waiting')) : friendName}</div>
<div class="lobby-player-status" id="lobby-opponent-status">${isHost ? 'في انتظار القبول...' : `${emoji('check', '✓', 11)} جاهز`}</div> <div class="lobby-player-status" id="lobby-opponent-status">${isHost ? t('lobby.waiting_accept') : `${emoji('check', '✓', 11)} ${t('lobby.ready')}`}</div>
${color ? `<div class="lobby-color" style="background:${color === 'w' ? '#1a1a1a' : '#fff'};border:2px solid ${color === 'w' ? '#475569' : '#e2e8f0'};width:20px;height:20px;border-radius:50%;margin-top:6px;"></div>` : ''} ${color ? `<div class="lobby-color" style="background:${color === 'w' ? '#1a1a1a' : '#fff'};border:2px solid ${color === 'w' ? '#475569' : '#e2e8f0'};width:20px;height:20px;border-radius:50%;margin-top:6px;"></div>` : ''}
</div> </div>
</div> </div>
<!-- Status --> <!-- Status -->
<div class="lobby-status" id="lobby-status"> <div class="lobby-status" id="lobby-status">
${isHost ? `<div class="lobby-status-text">${emoji('hourglass', '⏳', 14)} في انتظار الخصم...</div><div class="lobby-status-sub">سيتم بدء المباراة تلقائياً عند قبول التحدي</div>` : `<div class="lobby-status-text" style="color:#34D399;">${emoji('check', '✓', 14)} جاهز للبدء!</div>`} ${isHost ? `<div class="lobby-status-text">${emoji('hourglass', '⏳', 14)} ${t('lobby.waiting_opponent')}</div><div class="lobby-status-sub">${t('lobby.auto_start')}</div>` : `<div class="lobby-status-text" style="color:#34D399;">${emoji('check', '✓', 14)} ${t('lobby.ready_start')}</div>`}
</div> </div>
<!-- Actions --> <!-- Actions -->
<div class="lobby-actions"> <div class="lobby-actions">
${!isHost ? `<button class="btn btn-primary lobby-btn" id="lobby-start" style="background:#34D399;">${emoji('play', '▶', 14)} ابدأ المباراة</button>` : ''} ${!isHost ? `<button class="btn btn-primary lobby-btn" id="lobby-start" style="background:#34D399;">${emoji('play', '▶', 14)} ${t('lobby.start')}</button>` : ''}
<button class="btn btn-secondary lobby-btn" id="lobby-cancel">${emoji('exit', '✕', 12)} إلغاء</button> <button class="btn btn-secondary lobby-btn" id="lobby-cancel">${emoji('exit', '✕', 12)} ${t('common.cancel')}</button>
</div> </div>
</div> </div>
<style> <style>
...@@ -131,7 +132,7 @@ export function mountLobby(el, params = {}) { ...@@ -131,7 +132,7 @@ export function mountLobby(el, params = {}) {
// Guest: wait briefly so host can detect acceptance, then start // Guest: wait briefly so host can detect acceptance, then start
const statusEl = el.querySelector('#lobby-status'); const statusEl = el.querySelector('#lobby-status');
if (statusEl) { if (statusEl) {
statusEl.innerHTML = `<div class="lobby-status-text" style="color:#34D399;">${emoji('check', '✓', 14)} تم القبول! جاري تجهيز المباراة...</div>`; statusEl.innerHTML = `<div class="lobby-status-text" style="color:#34D399;">${emoji('check', '✓', 14)} ${t('lobby.accepted_preparing')}</div>`;
} }
setTimeout(() => startGame(el, params), 2500); setTimeout(() => startGame(el, params), 2500);
} }
...@@ -155,10 +156,10 @@ async function pollMatchStatus(el, params) { ...@@ -155,10 +156,10 @@ async function pollMatchStatus(el, params) {
const statusEl = el.querySelector('#lobby-status'); const statusEl = el.querySelector('#lobby-status');
const oppStatus = el.querySelector('#lobby-opponent-status'); const oppStatus = el.querySelector('#lobby-opponent-status');
if (statusEl) { if (statusEl) {
statusEl.innerHTML = `<div class="lobby-status-text" style="color:#34D399;">${emoji('check', '✓', 14)} الخصم قبل! جاري البدء...</div>`; statusEl.innerHTML = `<div class="lobby-status-text" style="color:#34D399;">${emoji('check', '✓', 14)} ${t('lobby.opponent_accepted')}</div>`;
} }
if (oppStatus) { if (oppStatus) {
oppStatus.innerHTML = `<span style="color:#34D399;">${emoji('check', '✓', 11)} جاهز</span>`; oppStatus.innerHTML = `<span style="color:#34D399;">${emoji('check', '✓', 11)} ${t('lobby.ready')}</span>`;
oppStatus.classList.add('ready'); oppStatus.classList.add('ready');
} }
...@@ -213,12 +214,12 @@ async function cancelAndLeave(el) { ...@@ -213,12 +214,12 @@ async function cancelAndLeave(el) {
} }
function formatTimeControl(tc) { function formatTimeControl(tc) {
if (tc.includes('bullet_1')) return '1 دقيقة'; if (tc.includes('bullet_1')) return t('time.1min');
if (tc.includes('blitz_3')) return '3 دقائق'; if (tc.includes('blitz_3')) return t('time.3min');
if (tc.includes('blitz_5')) return '5 دقائق'; if (tc.includes('blitz_5')) return t('time.5min');
if (tc.includes('rapid_10')) return '10 دقائق'; if (tc.includes('rapid_10')) return t('time.10min');
if (tc.includes('rapid_15')) return '15 دقيقة'; if (tc.includes('rapid_15')) return t('time.15min');
if (tc.includes('classical')) return '30 دقيقة'; if (tc.includes('classical')) return t('time.30min');
return tc; return tc;
} }
......
This diff is collapsed.
...@@ -5,38 +5,38 @@ import { t } from '../../../core/i18n.js'; ...@@ -5,38 +5,38 @@ import { t } from '../../../core/i18n.js';
const categories = [ const categories = [
{ {
name: 'Bullet', nameAr: 'رصاصة', icon: '⚡', color: '#FBBF24', name: 'Bullet', nameKey: 'time.bullet', icon: '⚡', color: '#FBBF24',
controls: [ controls: [
{ key: 'bullet_1_0', label: '1 دقيقة', sub: '1+0' }, { key: 'bullet_1_0', labelKey: 'time.1min', sub: '1+0' },
{ key: 'bullet_1_1', label: '1 | 1', sub: '1+1' }, { key: 'bullet_1_1', label: '1 | 1', sub: '1+1' },
{ key: 'bullet_2_1', label: '2 | 1', sub: '2+1' }, { key: 'bullet_2_1', label: '2 | 1', sub: '2+1' },
] ]
}, },
{ {
name: 'Blitz', nameAr: 'خاطفة', icon: '🔥', color: '#F97316', name: 'Blitz', nameKey: 'time.blitz', icon: '🔥', color: '#F97316',
controls: [ controls: [
{ key: 'blitz_3_0', label: '3 دقائق', sub: '3+0' }, { key: 'blitz_3_0', labelKey: 'time.3min', sub: '3+0' },
{ key: 'blitz_3_2', label: '3 | 2', sub: '3+2' }, { key: 'blitz_3_2', label: '3 | 2', sub: '3+2' },
{ key: 'blitz_5_0', label: '5 دقائق', sub: '5+0' }, { key: 'blitz_5_0', labelKey: 'time.5min', sub: '5+0' },
{ key: 'blitz_5_3', label: '5 | 3', sub: '5+3' }, { key: 'blitz_5_3', label: '5 | 3', sub: '5+3' },
{ key: 'blitz_5_5', label: '5 | 5', sub: '5+5' }, { key: 'blitz_5_5', label: '5 | 5', sub: '5+5' },
] ]
}, },
{ {
name: 'Rapid', nameAr: 'سريعة', icon: '🏃', color: '#22C55E', name: 'Rapid', nameKey: 'time.rapid', icon: '🏃', color: '#22C55E',
controls: [ controls: [
{ key: 'rapid_10_0', label: '10 دقائق', sub: '10+0' }, { key: 'rapid_10_0', labelKey: 'time.10min', sub: '10+0' },
{ key: 'rapid_10_5', label: '10 | 5', sub: '10+5' }, { key: 'rapid_10_5', label: '10 | 5', sub: '10+5' },
{ key: 'rapid_15_10', label: '15 | 10', sub: '15+10' }, { key: 'rapid_15_10', label: '15 | 10', sub: '15+10' },
{ key: 'rapid_20_0', label: '20 دقيقة', sub: '20+0' }, { key: 'rapid_20_0', labelKey: 'time.20min', sub: '20+0' },
{ key: 'rapid_30_0', label: '30 دقيقة', sub: '30+0' }, { key: 'rapid_30_0', labelKey: 'time.30min', sub: '30+0' },
] ]
}, },
{ {
name: 'Classical', nameAr: 'كلاسيكية', icon: '♔', color: '#8B5CF6', name: 'Classical', nameKey: 'time.classical', icon: '♔', color: '#8B5CF6',
controls: [ controls: [
{ key: 'classical_45_45', label: '45 | 45', sub: '45+45' }, { key: 'classical_45_45', label: '45 | 45', sub: '45+45' },
{ key: 'classical_60_0', label: '60 دقيقة', sub: '60+0' }, { key: 'classical_60_0', labelKey: 'time.60min', sub: '60+0' },
{ key: 'classical_90_30', label: '90 | 30', sub: '90+30' }, { key: 'classical_90_30', label: '90 | 30', sub: '90+30' },
] ]
} }
...@@ -53,14 +53,14 @@ export function mountTimeSelect(el, params) { ...@@ -53,14 +53,14 @@ export function mountTimeSelect(el, params) {
<div class="tc-category"> <div class="tc-category">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;"> <div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;">
<span style="font-size:18px;">${cat.icon}</span> <span style="font-size:18px;">${cat.icon}</span>
<span style="font-size:14px;font-weight:700;color:${cat.color};">${cat.nameAr}</span> <span style="font-size:14px;font-weight:700;color:${cat.color};">${t(cat.nameKey)}</span>
<span style="font-size:11px;color:#64748b;font-family:var(--font-lat);">${cat.name}</span> <span style="font-size:11px;color:#64748b;font-family:var(--font-lat);">${cat.name}</span>
</div> </div>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;"> <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;">
${cat.controls.map(tc => ` ${cat.controls.map(tc => `
<button class="time-btn" data-key="${tc.key}" style="display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;height:58px;background:#1e1e3a;border:1px solid rgba(255,255,255,0.08);border-radius:10px;cursor:pointer;transition:transform 0.1s,background 0.15s,border-color 0.15s;"> <button class="time-btn" data-key="${tc.key}" style="display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;height:58px;background:#1e1e3a;border:1px solid rgba(255,255,255,0.08);border-radius:10px;cursor:pointer;transition:transform 0.1s,background 0.15s,border-color 0.15s;">
<span style="font-size:14px;font-weight:700;color:#f8fafc;font-family:var(--font-lat);">${tc.sub}</span> <span style="font-size:14px;font-weight:700;color:#f8fafc;font-family:var(--font-lat);">${tc.sub}</span>
<span style="font-size:10px;color:#94a3b8;">${tc.label}</span> <span style="font-size:10px;color:#94a3b8;">${tc.labelKey ? t(tc.labelKey) : tc.label}</span>
</button> </button>
`).join('')} `).join('')}
</div> </div>
......
This diff is collapsed.
This diff is collapsed.
...@@ -16,7 +16,7 @@ export async function mountPuzzle(el) { ...@@ -16,7 +16,7 @@ export async function mountPuzzle(el) {
<div style="display:flex;flex-direction:column;height:100%;background:var(--bg-deep);"> <div style="display:flex;flex-direction:column;height:100%;background:var(--bg-deep);">
<div style="display:flex;align-items:center;justify-content:space-between;padding:var(--s-2) var(--s-3);background:var(--bg-base);"> <div style="display:flex;align-items:center;justify-content:space-between;padding:var(--s-2) var(--s-3);background:var(--bg-base);">
<button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:var(--s-1) var(--s-3);font-size:12px;">${t('game.back')}</button> <button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:var(--s-1) var(--s-3);font-size:12px;">${t('game.back')}</button>
<span style="font-size:14px;font-weight:600;">أحجية شطرنج</span> <span style="font-size:14px;font-weight:600;">${t('puzzle.title')}</span>
<span id="puzzle-rating" style="font-size:13px;color:var(--gold);font-family:var(--font-lat);"></span> <span id="puzzle-rating" style="font-size:13px;color:var(--gold);font-family:var(--font-lat);"></span>
</div> </div>
<div id="puzzle-board" style="flex:1;display:flex;align-items:center;justify-content:center;padding:var(--s-2);"></div> <div id="puzzle-board" style="flex:1;display:flex;align-items:center;justify-content:center;padding:var(--s-2);"></div>
...@@ -124,7 +124,7 @@ function onSolved(el) { ...@@ -124,7 +124,7 @@ function onSolved(el) {
board.interactive = false; board.interactive = false;
audio.play('win', 'reward'); audio.play('win', 'reward');
const status = el.querySelector('#puzzle-status'); const status = el.querySelector('#puzzle-status');
status.innerHTML = `${emoji('checkmark', '✅', 15)} أحسنت! حل صحيح`; status.innerHTML = `${emoji('checkmark', '✅', 15)} ${t('puzzle.correct')}`;
status.style.color = 'var(--win)'; status.style.color = 'var(--win)';
setTimeout(() => { setTimeout(() => {
...@@ -137,7 +137,7 @@ function onFailed(el) { ...@@ -137,7 +137,7 @@ function onFailed(el) {
board.interactive = false; board.interactive = false;
audio.play('lose', 'game'); audio.play('lose', 'game');
const status = el.querySelector('#puzzle-status'); const status = el.querySelector('#puzzle-status');
status.innerHTML = `${emoji('cross', '❌', 15)} حل خاطئ`; status.innerHTML = `${emoji('cross', '❌', 15)} ${t('puzzle.wrong')}`;
status.style.color = 'var(--loss)'; status.style.color = 'var(--loss)';
setTimeout(() => { setTimeout(() => {
......
...@@ -4,6 +4,7 @@ import * as audio from '../../../core/audio.js'; ...@@ -4,6 +4,7 @@ import * as audio from '../../../core/audio.js';
import * as bus from '../../../core/bus.js'; import * as bus from '../../../core/bus.js';
import * as store from '../../../core/store.js'; import * as store from '../../../core/store.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
import { t } from '../../../core/i18n.js';
let pollInterval = null; let pollInterval = null;
let countdownInterval = null; let countdownInterval = null;
...@@ -18,7 +19,7 @@ export function mountTournamentArena(el, params) { ...@@ -18,7 +19,7 @@ export function mountTournamentArena(el, params) {
<div style="display:flex;flex-direction:column;height:100%;background:#0a0a1a;"> <div style="display:flex;flex-direction:column;height:100%;background:#0a0a1a;">
<div style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);"> <div style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);">
<button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button> <button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button>
<span style="font-size:15px;font-weight:700;color:#f8fafc;">${emoji('lightning', '⚡', 15)} أرينا</span> <span style="font-size:15px;font-weight:700;color:#f8fafc;">${emoji('lightning', '⚡', 15)} ${t('tournament.arena')}</span>
</div> </div>
<div id="arena-content" style="flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;align-items:center;gap:16px;"> <div id="arena-content" style="flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;align-items:center;gap:16px;">
</div> </div>
...@@ -39,11 +40,11 @@ async function startArena(el, tournamentId, tournamentName) { ...@@ -39,11 +40,11 @@ async function startArena(el, tournamentId, tournamentName) {
content.innerHTML = ` content.innerHTML = `
<div style="text-align:center;margin-top:24px;"> <div style="text-align:center;margin-top:24px;">
<div class="radar-pulse" style="width:80px;height:80px;margin:0 auto 16px;border-radius:50%;background:radial-gradient(circle,#E4AC38 0%,transparent 70%);animation:pulse 1.5s infinite;"></div> <div class="radar-pulse" style="width:80px;height:80px;margin:0 auto 16px;border-radius:50%;background:radial-gradient(circle,#E4AC38 0%,transparent 70%);animation:pulse 1.5s infinite;"></div>
<div style="font-size:16px;font-weight:700;color:#f8fafc;">جاري البحث عن خصم...</div> <div style="font-size:16px;font-weight:700;color:#f8fafc;">${t('play.searching')}</div>
<div style="font-size:12px;color:#64748b;margin-top:4px;">${tournamentName || 'أرينا'}</div> <div style="font-size:12px;color:#64748b;margin-top:4px;">${tournamentName || t('tournament.arena')}</div>
</div> </div>
<div id="arena-standings" style="width:100%;max-width:320px;margin-top:16px;"></div> <div id="arena-standings" style="width:100%;max-width:320px;margin-top:16px;"></div>
<button class="btn btn-secondary" id="leave-arena" style="margin-top:auto;margin-bottom:16px;color:#ef4444;border-color:#ef4444;">مغادرة الأرينا</button> <button class="btn btn-secondary" id="leave-arena" style="margin-top:auto;margin-bottom:16px;color:#ef4444;border-color:#ef4444;">${t('tournament.leave_arena')}</button>
<style> <style>
@keyframes pulse { 0%,100% { transform:scale(1);opacity:0.6; } 50% { transform:scale(1.3);opacity:0.2; } } @keyframes pulse { 0%,100% { transform:scale(1);opacity:0.6; } 50% { transform:scale(1.3);opacity:0.2; } }
</style> </style>
...@@ -129,12 +130,12 @@ async function loadArenaStandings(el, tournamentId) { ...@@ -129,12 +130,12 @@ async function loadArenaStandings(el, tournamentId) {
const userId = store.get('auth.userId'); const userId = store.get('auth.userId');
if (standings.length === 0) { if (standings.length === 0) {
container.innerHTML = '<div style="text-align:center;color:#64748b;font-size:12px;">لا توجد نتائج بعد</div>'; container.innerHTML = `<div style="text-align:center;color:#64748b;font-size:12px;">${t('tournament.no_results')}</div>`;
return; return;
} }
container.innerHTML = ` container.innerHTML = `
<div style="font-size:13px;font-weight:700;color:#94a3b8;margin-bottom:8px;text-align:center;">المتصدرون</div> <div style="font-size:13px;font-weight:700;color:#94a3b8;margin-bottom:8px;text-align:center;">${t('tournament.leaderboard')}</div>
${standings.slice(0, 10).map((p, i) => { ${standings.slice(0, 10).map((p, i) => {
const isMe = p.player_id === userId; const isMe = p.player_id === userId;
const medals = ['🥇', '🥈', '🥉']; const medals = ['🥇', '🥈', '🥉'];
......
...@@ -3,6 +3,7 @@ import * as net from '../../../core/net.js'; ...@@ -3,6 +3,7 @@ import * as net from '../../../core/net.js';
import * as audio from '../../../core/audio.js'; import * as audio from '../../../core/audio.js';
import * as store from '../../../core/store.js'; import * as store from '../../../core/store.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
import { t } from '../../../core/i18n.js';
export async function mountTournamentBracket(el, params) { export async function mountTournamentBracket(el, params) {
const { tournamentId } = params; const { tournamentId } = params;
...@@ -12,10 +13,10 @@ export async function mountTournamentBracket(el, params) { ...@@ -12,10 +13,10 @@ export async function mountTournamentBracket(el, params) {
<div style="display:flex;flex-direction:column;height:100%;background:#0a0a1a;"> <div style="display:flex;flex-direction:column;height:100%;background:#0a0a1a;">
<div style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);"> <div style="display:flex;align-items:center;gap:12px;padding:10px 14px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);">
<button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button> <button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button>
<span style="font-size:15px;font-weight:700;color:#f8fafc;">الشجرة</span> <span style="font-size:15px;font-weight:700;color:#f8fafc;">${t('tournament.bracket')}</span>
</div> </div>
<div id="bracket-container" style="flex:1;overflow:auto;padding:12px;"> <div id="bracket-container" style="flex:1;overflow:auto;padding:12px;">
<div style="text-align:center;color:#64748b;padding:32px;">جاري التحميل...</div> <div style="text-align:center;color:#64748b;padding:32px;">${t('common.loading')}</div>
</div> </div>
</div> </div>
`; `;
...@@ -27,7 +28,7 @@ export async function mountTournamentBracket(el, params) { ...@@ -27,7 +28,7 @@ export async function mountTournamentBracket(el, params) {
const container = el.querySelector('#bracket-container'); const container = el.querySelector('#bracket-container');
if (!data.bracket || !data.matches || data.matches.length === 0) { if (!data.bracket || !data.matches || data.matches.length === 0) {
container.innerHTML = '<div style="text-align:center;color:#64748b;padding:32px;">لا توجد شجرة بعد</div>'; container.innerHTML = `<div style="text-align:center;color:#64748b;padding:32px;">${t('tournament.no_bracket_yet')}</div>`;
return; return;
} }
...@@ -47,7 +48,7 @@ export async function mountTournamentBracket(el, params) { ...@@ -47,7 +48,7 @@ export async function mountTournamentBracket(el, params) {
const gap = Math.pow(2, totalRounds - r) * 8; const gap = Math.pow(2, totalRounds - r) * 8;
html += `<div style="display:flex;flex-direction:column;justify-content:space-around;min-width:150px;gap:${gap}px;padding:8px 4px;">`; html += `<div style="display:flex;flex-direction:column;justify-content:space-around;min-width:150px;gap:${gap}px;padding:8px 4px;">`;
html += `<div style="text-align:center;font-size:11px;font-weight:700;color:#64748b;margin-bottom:8px;">${roundLabels[r - 1] || 'دور ' + r}</div>`; html += `<div style="text-align:center;font-size:11px;font-weight:700;color:#64748b;margin-bottom:8px;">${roundLabels[r - 1] || t('tournament.round', { n: r })}</div>`;
matches.forEach(m => { matches.forEach(m => {
const isMyMatch = (m.player_a_id === userId || m.player_b_id === userId); const isMyMatch = (m.player_a_id === userId || m.player_b_id === userId);
...@@ -56,7 +57,7 @@ export async function mountTournamentBracket(el, params) { ...@@ -56,7 +57,7 @@ export async function mountTournamentBracket(el, params) {
html += ` html += `
<div class="bracket-match" data-match-id="${m.match_id || ''}" style="background:${statusBg};border:1px solid ${borderColor};border-radius:8px;padding:6px 8px;position:relative;"> <div class="bracket-match" data-match-id="${m.match_id || ''}" style="background:${statusBg};border:1px solid ${borderColor};border-radius:8px;padding:6px 8px;position:relative;">
${isMyMatch ? '<div style="position:absolute;top:-6px;right:4px;font-size:9px;background:#E4AC38;color:#000;padding:1px 4px;border-radius:4px;font-weight:700;">أنت</div>' : ''} ${isMyMatch ? `<div style="position:absolute;top:-6px;right:4px;font-size:9px;background:#E4AC38;color:#000;padding:1px 4px;border-radius:4px;font-weight:700;">${t('common.you')}</div>` : ''}
<div style="display:flex;justify-content:space-between;align-items:center;padding:3px 0;${m.winner_id === m.player_a_id ? 'font-weight:700;' : ''}"> <div style="display:flex;justify-content:space-between;align-items:center;padding:3px 0;${m.winner_id === m.player_a_id ? 'font-weight:700;' : ''}">
<span style="font-size:11px;color:${m.winner_id === m.player_a_id ? '#34D399' : '#f8fafc'};max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${m.player_a_name || (m.player_a_id ? '...' : 'BYE')}</span> <span style="font-size:11px;color:${m.winner_id === m.player_a_id ? '#34D399' : '#f8fafc'};max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${m.player_a_name || (m.player_a_id ? '...' : 'BYE')}</span>
<span style="font-size:10px;color:#64748b;">${m.result ? m.result.split('-')[0] : ''}</span> <span style="font-size:10px;color:#64748b;">${m.result ? m.result.split('-')[0] : ''}</span>
...@@ -66,7 +67,7 @@ export async function mountTournamentBracket(el, params) { ...@@ -66,7 +67,7 @@ export async function mountTournamentBracket(el, params) {
<span style="font-size:11px;color:${m.winner_id === m.player_b_id ? '#34D399' : '#f8fafc'};max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${m.player_b_name || (m.player_b_id ? '...' : 'TBD')}</span> <span style="font-size:11px;color:${m.winner_id === m.player_b_id ? '#34D399' : '#f8fafc'};max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${m.player_b_name || (m.player_b_id ? '...' : 'TBD')}</span>
<span style="font-size:10px;color:#64748b;">${m.result ? m.result.split('-')[1] : ''}</span> <span style="font-size:10px;color:#64748b;">${m.result ? m.result.split('-')[1] : ''}</span>
</div> </div>
${m.status === 'pending' && isMyMatch && m.player_a_id && m.player_b_id ? `<button class="bracket-play-btn" data-mid="${m.match_id}" style="width:100%;margin-top:4px;padding:4px;background:#E4AC38;border:none;border-radius:4px;color:#000;font-weight:700;font-size:10px;cursor:pointer;">العب</button>` : ''} ${m.status === 'pending' && isMyMatch && m.player_a_id && m.player_b_id ? `<button class="bracket-play-btn" data-mid="${m.match_id}" style="width:100%;margin-top:4px;padding:4px;background:#E4AC38;border:none;border-radius:4px;color:#000;font-weight:700;font-size:10px;cursor:pointer;">${t('common.play')}</button>` : ''}
</div> </div>
`; `;
}); });
...@@ -94,7 +95,7 @@ export async function mountTournamentBracket(el, params) { ...@@ -94,7 +95,7 @@ export async function mountTournamentBracket(el, params) {
}); });
} catch (e) { } catch (e) {
el.querySelector('#bracket-container').innerHTML = '<div style="text-align:center;color:#ef4444;padding:32px;">فشل تحميل الشجرة</div>'; el.querySelector('#bracket-container').innerHTML = `<div style="text-align:center;color:#ef4444;padding:32px;">${t('tournament.bracket_load_failed')}</div>`;
} }
} }
...@@ -102,10 +103,10 @@ function getRoundLabels(totalRounds) { ...@@ -102,10 +103,10 @@ function getRoundLabels(totalRounds) {
const labels = []; const labels = [];
for (let r = 1; r <= totalRounds; r++) { for (let r = 1; r <= totalRounds; r++) {
const remaining = totalRounds - r; const remaining = totalRounds - r;
if (remaining === 0) labels.push('النهائي'); if (remaining === 0) labels.push(t('tournament.final'));
else if (remaining === 1) labels.push('نصف النهائي'); else if (remaining === 1) labels.push(t('tournament.semifinal'));
else if (remaining === 2) labels.push('ربع النهائي'); else if (remaining === 2) labels.push(t('tournament.quarterfinal'));
else labels.push('دور ' + Math.pow(2, remaining + 1)); else labels.push(t('tournament.round', { n: Math.pow(2, remaining + 1) }));
} }
return labels; return labels;
} }
...@@ -3,6 +3,7 @@ import * as net from '../../../core/net.js'; ...@@ -3,6 +3,7 @@ import * as net from '../../../core/net.js';
import * as audio from '../../../core/audio.js'; import * as audio from '../../../core/audio.js';
import * as bus from '../../../core/bus.js'; import * as bus from '../../../core/bus.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
import { t } from '../../../core/i18n.js';
import * as realtime from '../../../core/realtime.js'; import * as realtime from '../../../core/realtime.js';
let countdownInterval = null; let countdownInterval = null;
...@@ -17,29 +18,29 @@ export function mountTournamentLobby(el, params) { ...@@ -17,29 +18,29 @@ export function mountTournamentLobby(el, params) {
el.innerHTML = ` el.innerHTML = `
<div style="display:flex;flex-direction:column;height:100%;background:#0a0a1a;align-items:center;justify-content:center;padding:24px;gap:20px;"> <div style="display:flex;flex-direction:column;height:100%;background:#0a0a1a;align-items:center;justify-content:center;padding:24px;gap:20px;">
<div style="text-align:center;"> <div style="text-align:center;">
<div style="font-size:20px;font-weight:800;color:#f8fafc;">${tournamentName || 'بطولة'}</div> <div style="font-size:20px;font-weight:800;color:#f8fafc;">${tournamentName || t('tournament.title')}</div>
<div id="countdown" style="font-size:42px;font-weight:800;color:#E4AC38;margin-top:12px;font-family:Inter,monospace;">--:--</div> <div id="countdown" style="font-size:42px;font-weight:800;color:#E4AC38;margin-top:12px;font-family:Inter,monospace;">--:--</div>
<div style="font-size:13px;color:#64748b;margin-top:4px;">تبدأ خلال</div> <div style="font-size:13px;color:#64748b;margin-top:4px;">${t('tournament.starts_in', { n: '' })}</div>
</div> </div>
<div id="players-grid" style="display:flex;flex-wrap:wrap;gap:8px;justify-content:center;max-width:280px;"></div> <div id="players-grid" style="display:flex;flex-wrap:wrap;gap:8px;justify-content:center;max-width:280px;"></div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;width:100%;max-width:300px;"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;width:100%;max-width:300px;">
<div style="background:#1a1a2e;border-radius:8px;padding:10px;text-align:center;"> <div style="background:#1a1a2e;border-radius:8px;padding:10px;text-align:center;">
<div style="font-size:11px;color:#64748b;">النظام</div> <div style="font-size:11px;color:#64748b;">${t('tournament.format_label')}</div>
<div style="font-size:13px;font-weight:700;color:#f8fafc;">${format || 'swiss'}</div> <div style="font-size:13px;font-weight:700;color:#f8fafc;">${format || 'swiss'}</div>
</div> </div>
<div style="background:#1a1a2e;border-radius:8px;padding:10px;text-align:center;"> <div style="background:#1a1a2e;border-radius:8px;padding:10px;text-align:center;">
<div style="font-size:11px;color:#64748b;">الوقت</div> <div style="font-size:11px;color:#64748b;">${t('tournament.time_label')}</div>
<div style="font-size:13px;font-weight:700;color:#f8fafc;">${timeControl || '?'}</div> <div style="font-size:13px;font-weight:700;color:#f8fafc;">${timeControl || '?'}</div>
</div> </div>
<div style="background:#1a1a2e;border-radius:8px;padding:10px;text-align:center;"> <div style="background:#1a1a2e;border-radius:8px;padding:10px;text-align:center;">
<div style="font-size:11px;color:#64748b;">الجوائز</div> <div style="font-size:11px;color:#64748b;">${t('tournament.prizes_label')}</div>
<div style="font-size:13px;font-weight:700;color:#E4AC38;">${prizePool || '0'} ${emoji('coin', '🪙', 13)}</div> <div style="font-size:13px;font-weight:700;color:#E4AC38;">${prizePool || '0'} ${emoji('coin', '🪙', 13)}</div>
</div> </div>
</div> </div>
<button class="btn btn-secondary" id="leave-lobby" style="margin-top:auto;color:#ef4444;border-color:#ef4444;font-size:13px;">مغادرة البطولة</button> <button class="btn btn-secondary" id="leave-lobby" style="margin-top:auto;color:#ef4444;border-color:#ef4444;font-size:13px;">${t('tournament.leave')}</button>
</div> </div>
`; `;
...@@ -96,7 +97,7 @@ async function loadPlayers(el, tournamentId) { ...@@ -96,7 +97,7 @@ async function loadPlayers(el, tournamentId) {
const registrations = data.registrations || []; const registrations = data.registrations || [];
if (registrations.length === 0) { if (registrations.length === 0) {
grid.innerHTML = '<div style="font-size:12px;color:#64748b;">في انتظار اللاعبين...</div>'; grid.innerHTML = `<div style="font-size:12px;color:#64748b;">${t('tournament.waiting')}</div>`;
return; return;
} }
......
...@@ -33,7 +33,7 @@ export async function mountTournaments(el) { ...@@ -33,7 +33,7 @@ export async function mountTournaments(el) {
function renderTournaments(el, tournaments) { function renderTournaments(el, tournaments) {
const list = el.querySelector('#tournament-list'); const list = el.querySelector('#tournament-list');
if (tournaments.length === 0) { if (tournaments.length === 0) {
list.innerHTML = `<p style="color:#94a3b8;text-align:center;padding:32px;">لا توجد بطولات حالياً</p>`; list.innerHTML = `<p style="color:#94a3b8;text-align:center;padding:32px;">${t('tournament.no_tournaments')}</p>`;
return; return;
} }
...@@ -41,7 +41,7 @@ function renderTournaments(el, tournaments) { ...@@ -41,7 +41,7 @@ function renderTournaments(el, tournaments) {
<div class="card tournament-card" data-id="${tour.id}" style="padding:16px;margin-bottom:12px;cursor:pointer;"> <div class="card tournament-card" data-id="${tour.id}" style="padding:16px;margin-bottom:12px;cursor:pointer;">
<div style="display:flex;justify-content:space-between;align-items:start;"> <div style="display:flex;justify-content:space-between;align-items:start;">
<div> <div>
<div style="font-size:15px;font-weight:700;">${tour.name || 'بطولة'}</div> <div style="font-size:15px;font-weight:700;">${tour.name || t('tournament.title')}</div>
<div style="font-size:12px;color:#94a3b8;margin-top:2px;">${tour.game_key || 'chess'} · ${formatName(tour.format)}</div> <div style="font-size:12px;color:#94a3b8;margin-top:2px;">${tour.game_key || 'chess'} · ${formatName(tour.format)}</div>
<div style="font-size:11px;color:#64748b;margin-top:4px;">${tour.starts_at ? new Date(tour.starts_at).toLocaleDateString('ar') : ''}</div> <div style="font-size:11px;color:#64748b;margin-top:4px;">${tour.starts_at ? new Date(tour.starts_at).toLocaleDateString('ar') : ''}</div>
</div> </div>
...@@ -49,10 +49,10 @@ function renderTournaments(el, tournaments) { ...@@ -49,10 +49,10 @@ function renderTournaments(el, tournaments) {
</div> </div>
<div style="display:flex;gap:16px;margin-top:12px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.05);"> <div style="display:flex;gap:16px;margin-top:12px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.05);">
<span style="font-size:11px;color:#94a3b8;">${emoji('people', '👥', 11)} ${tour.player_count || 0}/${tour.max_players || 32}</span> <span style="font-size:11px;color:#94a3b8;">${emoji('people', '👥', 11)} ${tour.player_count || 0}/${tour.max_players || 32}</span>
<span style="font-size:11px;color:#E4AC38;">${emoji('tournament_cup', '🏆', 11)} ${tour.prize_pool_coins ? tour.prize_pool_coins + ' عملة' : 'N/A'}</span> <span style="font-size:11px;color:#E4AC38;">${emoji('tournament_cup', '🏆', 11)} ${tour.prize_pool_coins ? tour.prize_pool_coins + ' ' + t('common.coins') : 'N/A'}</span>
${tour.entry_fee_coins ? `<span style="font-size:11px;color:#F87171;">${emoji('money_bag', '💰', 11)} ${tour.entry_fee_coins}</span>` : ''} ${tour.entry_fee_coins ? `<span style="font-size:11px;color:#F87171;">${emoji('money_bag', '💰', 11)} ${tour.entry_fee_coins}</span>` : ''}
</div> </div>
${tour.status === 'registration' ? `<button class="btn btn-primary w-full register-btn" data-tid="${tour.id}" style="margin-top:12px;font-size:13px;">سجّل الآن</button>` : ''} ${tour.status === 'registration' ? `<button class="btn btn-primary w-full register-btn" data-tid="${tour.id}" style="margin-top:12px;font-size:13px;">${t('tournament.register_now')}</button>` : ''}
</div> </div>
`).join(''); `).join('');
...@@ -63,13 +63,13 @@ function renderTournaments(el, tournaments) { ...@@ -63,13 +63,13 @@ function renderTournaments(el, tournaments) {
audio.play('click'); audio.play('click');
const tid = btn.dataset.tid; const tid = btn.dataset.tid;
btn.disabled = true; btn.disabled = true;
btn.textContent = 'جاري التسجيل...'; btn.textContent = t('tournament.registering');
try { try {
await net.post('tournaments.php', { action: 'register', tournament_id: tid }); await net.post('tournaments.php', { action: 'register', tournament_id: tid });
btn.textContent = '✅ تم التسجيل'; btn.textContent = '✅ ' + t('tournament.registered');
btn.style.background = '#34D399'; btn.style.background = '#34D399';
} catch (err) { } catch (err) {
btn.textContent = err.message || 'فشل التسجيل'; btn.textContent = err.message || t('tournament.register_failed');
btn.disabled = false; btn.disabled = false;
} }
}); });
...@@ -88,19 +88,19 @@ async function showTournamentDetail(el, tournamentId, tour) { ...@@ -88,19 +88,19 @@ async function showTournamentDetail(el, tournamentId, tour) {
const list = el.querySelector('#tournament-list'); const list = el.querySelector('#tournament-list');
list.innerHTML = ` list.innerHTML = `
<div style="margin-bottom:16px;"> <div style="margin-bottom:16px;">
<button class="btn btn-secondary" id="detail-back" style="font-size:12px;min-height:32px;padding:4px 12px;">← رجوع للقائمة</button> <button class="btn btn-secondary" id="detail-back" style="font-size:12px;min-height:32px;padding:4px 12px;">← ${t('tournament.back_to_list')}</button>
</div> </div>
<div class="card" style="padding:16px;"> <div class="card" style="padding:16px;">
<div style="font-size:18px;font-weight:700;margin-bottom:4px;">${tour?.name || 'بطولة'}</div> <div style="font-size:18px;font-weight:700;margin-bottom:4px;">${tour?.name || t('tournament.title')}</div>
<div style="font-size:12px;color:#94a3b8;margin-bottom:12px;">${formatName(tour?.format)} · ${tour?.game_key || 'chess'}</div> <div style="font-size:12px;color:#94a3b8;margin-bottom:12px;">${formatName(tour?.format)} · ${tour?.game_key || 'chess'}</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px;"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px;">
<div style="background:#1a2440;padding:8px;border-radius:8px;text-align:center;"> <div style="background:#1a2440;padding:8px;border-radius:8px;text-align:center;">
<div style="font-size:16px;font-weight:700;color:#E4AC38;">${tour?.player_count || 0}</div> <div style="font-size:16px;font-weight:700;color:#E4AC38;">${tour?.player_count || 0}</div>
<div style="font-size:10px;color:#64748b;">لاعبين</div> <div style="font-size:10px;color:#64748b;">${t('tournament.players_label')}</div>
</div> </div>
<div style="background:#1a2440;padding:8px;border-radius:8px;text-align:center;"> <div style="background:#1a2440;padding:8px;border-radius:8px;text-align:center;">
<div style="font-size:16px;font-weight:700;color:#00FFFF;">${tour?.rounds_total || tour?.swiss_rounds || '?'}</div> <div style="font-size:16px;font-weight:700;color:#00FFFF;">${tour?.rounds_total || tour?.swiss_rounds || '?'}</div>
<div style="font-size:10px;color:#64748b;">جولات</div> <div style="font-size:10px;color:#64748b;">${t('tournament.rounds_label')}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -119,20 +119,20 @@ async function showTournamentDetail(el, tournamentId, tour) { ...@@ -119,20 +119,20 @@ async function showTournamentDetail(el, tournamentId, tour) {
const data = await net.get('tournaments.php', { action: 'detail', id: tournamentId }); const data = await net.get('tournaments.php', { action: 'detail', id: tournamentId });
renderBracketOrStandings(el, data, tour?.format); renderBracketOrStandings(el, data, tour?.format);
} catch (e) { } catch (e) {
el.querySelector('#bracket-area').innerHTML = `<p style="color:#64748b;text-align:center;">لا توجد نتائج بعد</p>`; el.querySelector('#bracket-area').innerHTML = `<p style="color:#64748b;text-align:center;">${t('tournament.no_results_yet')}</p>`;
} }
} }
function renderBracketOrStandings(el, data, format) { function renderBracketOrStandings(el, data, format) {
const area = el.querySelector('#bracket-area'); const area = el.querySelector('#bracket-area');
if (!data || (!data.rounds && !data.brackets)) { if (!data || (!data.rounds && !data.brackets)) {
area.innerHTML = `<p style="color:#64748b;text-align:center;padding:16px;">لا توجد نتائج بعد</p>`; area.innerHTML = `<p style="color:#64748b;text-align:center;padding:16px;">${t('tournament.no_results_yet')}</p>`;
return; return;
} }
area.innerHTML = ` area.innerHTML = `
<div class="card" style="padding:12px;"> <div class="card" style="padding:12px;">
<div style="font-size:14px;font-weight:600;margin-bottom:8px;">النتائج</div> <div style="font-size:14px;font-weight:600;margin-bottom:8px;">${t('tournament.results')}</div>
<div style="font-size:12px;color:#94a3b8;">بيانات البطولة متاحة عند بدء الجولات</div> <div style="font-size:12px;color:#94a3b8;">${t('tournament.data_available_later')}</div>
</div> </div>
`; `;
} }
...@@ -148,15 +148,15 @@ function getStatusColor(status) { ...@@ -148,15 +148,15 @@ function getStatusColor(status) {
function getStatusLabel(status) { function getStatusLabel(status) {
switch (status) { switch (status) {
case 'registration': return 'تسجيل مفتوح'; case 'registration': return t('tournament.registration_open');
case 'in_progress': return 'جارية'; case 'in_progress': return t('tournament.active');
case 'completed': return 'منتهية'; case 'completed': return t('tournament.completed');
case 'draft': return 'قريباً'; case 'draft': return t('tournament.coming_soon');
default: return status || 'قادمة'; default: return status || t('tournament.upcoming');
} }
} }
function formatName(format) { function formatName(format) {
const names = { swiss: 'سويسري', round_robin: 'دوري', single_elimination: 'خروج المغلوب', double_elimination: 'خروج مزدوج', arena: 'أرينا' }; const names = { swiss: t('tournament.format_swiss'), round_robin: t('tournament.format_round_robin'), single_elimination: t('tournament.format_single_elim'), double_elimination: t('tournament.format_double_elim'), arena: t('tournament.format_arena') };
return names[format] || format || ''; return names[format] || format || '';
} }
...@@ -2,13 +2,14 @@ import * as net from '../../../core/net.js'; ...@@ -2,13 +2,14 @@ import * as net from '../../../core/net.js';
import * as audio from '../../../core/audio.js'; import * as audio from '../../../core/audio.js';
import * as scene from '../../../core/scene.js'; import * as scene from '../../../core/scene.js';
import * as juice from '../../../core/juice.js'; import * as juice from '../../../core/juice.js';
import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
const CATEGORY_LABELS = { const CATEGORY_LABELS = {
gameplay: 'اللعب', gameplay: () => t('achievements.gameplay'),
social: 'اجتماعي', social: () => t('achievements.social'),
progression: 'التقدم', progression: () => t('achievements.progression'),
collection: 'جمع', collection: () => t('achievements.collection'),
}; };
const TIER_COLORS = { const TIER_COLORS = {
...@@ -19,7 +20,7 @@ const TIER_COLORS = { ...@@ -19,7 +20,7 @@ const TIER_COLORS = {
}; };
export async function mountAchievements(el) { export async function mountAchievements(el) {
el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;background:#0a0a1a;color:#64748b;">جاري التحميل...</div>`; el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;background:#0a0a1a;color:#64748b;">${t('common.loading')}</div>`;
let achievements = []; let achievements = [];
let stats = { total: 0, completed: 0 }; let stats = { total: 0, completed: 0 };
...@@ -57,7 +58,7 @@ function render(el, achievements, stats) { ...@@ -57,7 +58,7 @@ function render(el, achievements, stats) {
<!-- Header --> <!-- Header -->
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);"> <div style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);">
<button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button> <button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button>
<span style="font-size:16px;font-weight:700;color:#f8fafc;">${emoji('trophy', '🏆', 16)} الإنجازات</span> <span style="font-size:16px;font-weight:700;color:#f8fafc;">${emoji('trophy', '🏆', 16)} ${t('achievements.title')}</span>
<span style="margin-inline-start:auto;font-size:12px;color:#64748b;">${stats.completed}/${stats.total}</span> <span style="margin-inline-start:auto;font-size:12px;color:#64748b;">${stats.completed}/${stats.total}</span>
</div> </div>
...@@ -67,15 +68,15 @@ function render(el, achievements, stats) { ...@@ -67,15 +68,15 @@ function render(el, achievements, stats) {
<div style="background:linear-gradient(90deg,#E4AC38,#FFCC66);height:100%;width:${progressPct}%;border-radius:99px;transition:width 0.5s;"></div> <div style="background:linear-gradient(90deg,#E4AC38,#FFCC66);height:100%;width:${progressPct}%;border-radius:99px;transition:width 0.5s;"></div>
</div> </div>
<div style="display:flex;justify-content:space-between;margin-top:4px;"> <div style="display:flex;justify-content:space-between;margin-top:4px;">
<span style="font-size:10px;color:#64748b;">${progressPct}% مكتمل</span> <span style="font-size:10px;color:#64748b;">${progressPct}% ${t('achievements.complete')}</span>
<span style="font-size:10px;color:#E4AC38;">${stats.completed} إنجاز</span> <span style="font-size:10px;color:#E4AC38;">${stats.completed} ${t('achievements.title')}</span>
</div> </div>
</div> </div>
<!-- Category filter --> <!-- Category filter -->
<div style="display:flex;gap:6px;padding:12px 16px;overflow-x:auto;"> <div style="display:flex;gap:6px;padding:12px 16px;overflow-x:auto;">
<button class="ach-filter active" data-cat="all">الكل</button> <button class="ach-filter active" data-cat="all">${t('achievements.all')}</button>
${categories.map(c => `<button class="ach-filter" data-cat="${c}">${CATEGORY_LABELS[c] || c}</button>`).join('')} ${categories.map(c => `<button class="ach-filter" data-cat="${c}">${CATEGORY_LABELS[c] ? CATEGORY_LABELS[c]() : c}</button>`).join('')}
</div> </div>
<!-- Achievement list --> <!-- Achievement list -->
...@@ -106,7 +107,7 @@ function render(el, achievements, stats) { ...@@ -106,7 +107,7 @@ function render(el, achievements, stats) {
function renderList(achievements, category) { function renderList(achievements, category) {
const filtered = category === 'all' ? achievements : achievements.filter(a => a.category === category); const filtered = category === 'all' ? achievements : achievements.filter(a => a.category === category);
if (filtered.length === 0) { if (filtered.length === 0) {
return `<div style="text-align:center;padding:32px;color:#475569;font-size:13px;">لا توجد إنجازات في هذه الفئة</div>`; return `<div style="text-align:center;padding:32px;color:#475569;font-size:13px;">${t('achievements.none')}</div>`;
} }
return filtered.map(a => { return filtered.map(a => {
...@@ -131,12 +132,12 @@ function renderList(achievements, category) { ...@@ -131,12 +132,12 @@ function renderList(achievements, category) {
<span style="font-size:10px;color:#64748b;white-space:nowrap;">${a.progress}/${a.target}</span> <span style="font-size:10px;color:#64748b;white-space:nowrap;">${a.progress}/${a.target}</span>
</div> </div>
` : ` ` : `
<div style="font-size:10px;color:#22c55e;">✓ مكتمل</div> <div style="font-size:10px;color:#22c55e;">✓ ${t('achievements.complete')}</div>
`} `}
</div> </div>
<div style="text-align:center;min-width:40px;"> <div style="text-align:center;min-width:40px;">
<div style="font-size:11px;font-weight:700;color:#E4AC38;">${a.coins_reward}</div> <div style="font-size:11px;font-weight:700;color:#E4AC38;">${a.coins_reward}</div>
<div style="font-size:9px;color:#64748b;">عملة</div> <div style="font-size:9px;color:#64748b;">${t('game.coins')}</div>
</div> </div>
</div> </div>
`; `;
......
...@@ -4,6 +4,7 @@ import * as juice from '../../../core/juice.js'; ...@@ -4,6 +4,7 @@ import * as juice from '../../../core/juice.js';
import * as bus from '../../../core/bus.js'; import * as bus from '../../../core/bus.js';
import * as store from '../../../core/store.js'; import * as store from '../../../core/store.js';
import * as scene from '../../../core/scene.js'; import * as scene from '../../../core/scene.js';
import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
export async function mountChallenges(el) { export async function mountChallenges(el) {
...@@ -11,13 +12,13 @@ export async function mountChallenges(el) { ...@@ -11,13 +12,13 @@ export async function mountChallenges(el) {
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;"> <div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
<div style="display:flex;align-items:center;gap:12px;"> <div style="display:flex;align-items:center;gap:12px;">
<button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button> <button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button>
<h2 style="font-size:18px;font-weight:800;color:#f8fafc;flex:1;">${emoji('lightning', '⚡', 18)} التحديات اليومية</h2> <h2 style="font-size:18px;font-weight:800;color:#f8fafc;flex:1;">${emoji('lightning', '⚡', 18)} ${t('challenges.title')}</h2>
<div id="streak-badge" style="background:linear-gradient(135deg,#E4AC38,#FFCC66);color:#1a1a1a;font-size:12px;font-weight:800;padding:5px 12px;border-radius:99px;"></div> <div id="streak-badge" style="background:linear-gradient(135deg,#E4AC38,#FFCC66);color:#1a1a1a;font-size:12px;font-weight:800;padding:5px 12px;border-radius:99px;"></div>
</div> </div>
<div id="challenges-list"></div> <div id="challenges-list"></div>
<div id="all-done" style="display:none;text-align:center;padding:20px;"> <div id="all-done" style="display:none;text-align:center;padding:20px;">
<div style="font-size:40px;margin-bottom:8px;">${emoji('checkmark', '✅', 40)}</div> <div style="font-size:40px;margin-bottom:8px;">${emoji('checkmark', '✅', 40)}</div>
<div style="font-size:15px;font-weight:700;color:#34D399;">أنجزت كل التحديات اليوم!</div> <div style="font-size:15px;font-weight:700;color:#34D399;">${t('challenges.all_done')}</div>
</div> </div>
</div> </div>
`; `;
...@@ -28,14 +29,14 @@ export async function mountChallenges(el) { ...@@ -28,14 +29,14 @@ export async function mountChallenges(el) {
const data = await net.get('challenges.php'); const data = await net.get('challenges.php');
renderChallenges(el, data); renderChallenges(el, data);
} catch (e) { } catch (e) {
el.querySelector('#challenges-list').innerHTML = '<div style="text-align:center;color:#ef4444;">فشل التحميل</div>'; el.querySelector('#challenges-list').innerHTML = `<div style="text-align:center;color:#ef4444;">${t('common.error_load')}</div>`;
} }
} }
function renderChallenges(el, data) { function renderChallenges(el, data) {
const { challenges, streak, streak_bonus } = data; const { challenges, streak, streak_bonus } = data;
el.querySelector('#streak-badge').textContent = `🔥 ${streak || 0} يوم`; el.querySelector('#streak-badge').textContent = `🔥 ${t('challenges.streak', { n: streak || 0 })}`;
const list = el.querySelector('#challenges-list'); const list = el.querySelector('#challenges-list');
list.innerHTML = challenges.map((c, i) => ` list.innerHTML = challenges.map((c, i) => `
...@@ -52,7 +53,7 @@ function renderChallenges(el, data) { ...@@ -52,7 +53,7 @@ function renderChallenges(el, data) {
</div> </div>
<div style="text-align:center;min-width:50px;"> <div style="text-align:center;min-width:50px;">
${c.claimed ? '<span style="color:#34D399;font-size:16px;">✓</span>' : ${c.claimed ? '<span style="color:#34D399;font-size:16px;">✓</span>' :
c.completed ? `<button class="claim-btn" data-id="${c.id}" data-coins="${c.reward_coins}" data-xp="${c.reward_xp}" style="background:#E4AC38;color:#1a1a1a;border:none;border-radius:8px;padding:6px 10px;font-size:11px;font-weight:700;cursor:pointer;">اجمع</button>` : c.completed ? `<button class="claim-btn" data-id="${c.id}" data-coins="${c.reward_coins}" data-xp="${c.reward_xp}" style="background:#E4AC38;color:#1a1a1a;border:none;border-radius:8px;padding:6px 10px;font-size:11px;font-weight:700;cursor:pointer;">${t('challenges.claim')}</button>` :
`<span style="font-size:11px;color:#E4AC38;font-weight:600;">${c.reward_coins}${emoji('coin', '🪙', 14)}</span>`} `<span style="font-size:11px;color:#E4AC38;font-weight:600;">${c.reward_coins}${emoji('coin', '🪙', 14)}</span>`}
</div> </div>
</div> </div>
......
...@@ -10,7 +10,7 @@ import { emoji } from '../../../core/theme.js'; ...@@ -10,7 +10,7 @@ import { emoji } from '../../../core/theme.js';
const DAY_REWARDS = [50, 75, 100, 125, 150, 200, 300]; const DAY_REWARDS = [50, 75, 100, 125, 150, 200, 300];
export async function mountDaily(el) { export async function mountDaily(el) {
el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;background:#0a0a1a;color:#64748b;">جاري التحميل...</div>`; el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;background:#0a0a1a;color:#64748b;">${t('common.loading')}</div>`;
let streak = 0; let streak = 0;
let alreadyClaimed = false; let alreadyClaimed = false;
...@@ -45,14 +45,14 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) { ...@@ -45,14 +45,14 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) {
<!-- Header --> <!-- Header -->
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);"> <div style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:#0f0f1e;border-bottom:1px solid rgba(255,255,255,0.06);">
<button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button> <button class="btn btn-secondary" id="back-btn" style="min-height:32px;padding:4px 12px;font-size:12px;">←</button>
<span style="font-size:16px;font-weight:700;color:#f8fafc;">${emoji('gift', '🎁', 16)} المكافأة اليومية</span> <span style="font-size:16px;font-weight:700;color:#f8fafc;">${emoji('gift', '🎁', 16)} ${t('daily.title')}</span>
</div> </div>
<div style="flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;align-items:center;gap:16px;"> <div style="flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;align-items:center;gap:16px;">
<!-- Streak badge --> <!-- Streak badge -->
<div style="background:linear-gradient(135deg,#92400e,#E4AC38);padding:8px 20px;border-radius:99px;display:flex;align-items:center;gap:6px;"> <div style="background:linear-gradient(135deg,#92400e,#E4AC38);padding:8px 20px;border-radius:99px;display:flex;align-items:center;gap:6px;">
<span style="font-size:18px;">🔥</span> <span style="font-size:18px;">🔥</span>
<span style="font-size:14px;font-weight:800;color:#1a1a1a;">${streak} يوم متتالي</span> <span style="font-size:14px;font-weight:800;color:#1a1a1a;">${t('daily.streak_days', { n: streak })}</span>
</div> </div>
<!-- 7-day timeline --> <!-- 7-day timeline -->
...@@ -69,7 +69,7 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) { ...@@ -69,7 +69,7 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) {
color:${claimed ? '#34D399' : isToday ? '#1a1a1a' : '#64748b'};"> color:${claimed ? '#34D399' : isToday ? '#1a1a1a' : '#64748b'};">
${claimed ? '✓' : coins} ${claimed ? '✓' : coins}
</div> </div>
<span style="font-size:9px;color:${isToday ? '#E4AC38' : '#475569'};font-weight:${isToday ? '700' : '400'};">يوم ${i + 1}</span> <span style="font-size:9px;color:${isToday ? '#E4AC38' : '#475569'};font-weight:${isToday ? '700' : '400'};">${t('daily.day', { n: i + 1 })}</span>
</div> </div>
`; `;
}).join('')} }).join('')}
...@@ -79,19 +79,19 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) { ...@@ -79,19 +79,19 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) {
<div style="text-align:center;margin-top:8px;"> <div style="text-align:center;margin-top:8px;">
${alreadyClaimed ? ` ${alreadyClaimed ? `
<div style="font-size:48px;margin-bottom:8px;">${emoji('checkmark', '', 48)}</div> <div style="font-size:48px;margin-bottom:8px;">${emoji('checkmark', '', 48)}</div>
<div style="font-size:16px;font-weight:700;color:#34D399;">تم استلام مكافأة اليوم</div> <div style="font-size:16px;font-weight:700;color:#34D399;">${t('daily.claimed_today')}</div>
<div style="font-size:13px;color:#64748b;margin-top:4px;">عد غداً لمكافأة أكبر!</div> <div style="font-size:13px;color:#64748b;margin-top:4px;">${t('daily.come_back')}</div>
` : ` ` : `
<div style="font-size:56px;margin-bottom:8px;animation:float 3s ease-in-out infinite;">${emoji('gift', '🎁', 56)}</div> <div style="font-size:56px;margin-bottom:8px;animation:float 3s ease-in-out infinite;">${emoji('gift', '🎁', 56)}</div>
<div style="font-size:16px;font-weight:700;color:#f8fafc;margin-bottom:4px;">مكافأة اليوم</div> <div style="font-size:16px;font-weight:700;color:#f8fafc;margin-bottom:4px;">${t('daily.today_reward')}</div>
<div style="font-size:28px;font-weight:800;color:#E4AC38;margin-bottom:16px;">${todayReward} ${emoji('coin', '🪙', 24)}</div> <div style="font-size:28px;font-weight:800;color:#E4AC38;margin-bottom:16px;">${todayReward} ${emoji('coin', '🪙', 24)}</div>
<button class="btn btn-primary" id="claim-btn" style="font-size:16px;padding:14px 48px;">استلم المكافأة</button> <button class="btn btn-primary" id="claim-btn" style="font-size:16px;padding:14px 48px;">${t('daily.claim_btn')}</button>
`} `}
</div> </div>
<!-- Info --> <!-- Info -->
<div style="text-align:center;color:#475569;font-size:11px;margin-top:auto;padding:12px;"> <div style="text-align:center;color:#475569;font-size:11px;margin-top:auto;padding:12px;">
كل يوم تجمع فيه المكافأة يزيد المبلغ • اليوم السابع = 300 عملة! ${t('daily.hint')}
</div> </div>
</div> </div>
</div> </div>
...@@ -103,13 +103,13 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) { ...@@ -103,13 +103,13 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) {
if (claimBtn) { if (claimBtn) {
claimBtn.addEventListener('click', async () => { claimBtn.addEventListener('click', async () => {
claimBtn.disabled = true; claimBtn.disabled = true;
claimBtn.textContent = 'جاري الاستلام...'; claimBtn.textContent = t('daily.claiming');
try { try {
const data = await net.post('daily-reward.php', { action: 'claim' }); const data = await net.post('daily-reward.php', { action: 'claim' });
if (data.error) { if (data.error) {
claimBtn.textContent = data.error; claimBtn.textContent = data.error;
setTimeout(() => { claimBtn.textContent = 'استلم المكافأة'; claimBtn.disabled = false; }, 2000); setTimeout(() => { claimBtn.textContent = t('daily.claim_btn'); claimBtn.disabled = false; }, 2000);
return; return;
} }
...@@ -132,7 +132,7 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) { ...@@ -132,7 +132,7 @@ function render(el, streak, alreadyClaimed, dayIndex, todayReward) {
// Re-render with claimed state // Re-render with claimed state
render(el, data.streak, true, data.day_index, data.coins); render(el, data.streak, true, data.day_index, data.coins);
} catch (e) { } catch (e) {
claimBtn.textContent = 'فشل حاول مرة أخرى'; claimBtn.textContent = t('daily.failed');
claimBtn.disabled = false; claimBtn.disabled = false;
} }
}); });
......
import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
// Rank tier system // Rank tier system
export const TIERS = [ export const TIERS = [
{ name: 'برونزي', nameEn: 'Bronze', min: 0, max: 999, color: '#CD7F32', get icon() { return emoji('medal_bronze', '🥉', 16); } }, { get name() { return t('rank.bronze'); }, nameEn: 'Bronze', min: 0, max: 999, color: '#CD7F32', get icon() { return emoji('medal_bronze', '🥉', 16); } },
{ name: 'فضي', nameEn: 'Silver', min: 1000, max: 1199, color: '#C0C0C0', get icon() { return emoji('medal_silver', '🥈', 16); } }, { get name() { return t('rank.silver'); }, nameEn: 'Silver', min: 1000, max: 1199, color: '#C0C0C0', get icon() { return emoji('medal_silver', '🥈', 16); } },
{ name: 'ذهبي', nameEn: 'Gold', min: 1200, max: 1399, color: '#FFD700', get icon() { return emoji('medal_gold', '🥇', 16); } }, { get name() { return t('rank.gold'); }, nameEn: 'Gold', min: 1200, max: 1399, color: '#FFD700', get icon() { return emoji('medal_gold', '🥇', 16); } },
{ name: 'بلاتيني', nameEn: 'Platinum', min: 1400, max: 1599, color: '#00CED1', get icon() { return emoji('gem', '💎', 16); } }, { get name() { return t('rank.platinum'); }, nameEn: 'Platinum', min: 1400, max: 1599, color: '#00CED1', get icon() { return emoji('gem', '💎', 16); } },
{ name: 'ماسي', nameEn: 'Diamond', min: 1600, max: 1799, color: '#B9F2FF', icon: '💠' }, { get name() { return t('rank.diamond'); }, nameEn: 'Diamond', min: 1600, max: 1799, color: '#B9F2FF', icon: '💠' },
{ name: 'أسطوري', nameEn: 'Master', min: 1800, max: 2000, color: '#FF4500', icon: '👑' }, { get name() { return t('rank.master'); }, nameEn: 'Master', min: 1800, max: 2000, color: '#FF4500', icon: '👑' },
{ name: 'جراند ماستر', nameEn: 'Grandmaster', min: 2000, max: 9999, color: '#8B008B', icon: '🏅' }, { get name() { return t('rank.grandmaster'); }, nameEn: 'Grandmaster', min: 2000, max: 9999, color: '#8B008B', icon: '🏅' },
]; ];
export function getTier(rating) { export function getTier(rating) {
......
...@@ -73,7 +73,7 @@ async function purchasePrompt(el, item) { ...@@ -73,7 +73,7 @@ async function purchasePrompt(el, item) {
<div style="font-size:14px;color:var(--gold);margin-bottom:var(--s-4);">${item.price_coins || 0} ${emoji('coin', '🪙', 16)}</div> <div style="font-size:14px;color:var(--gold);margin-bottom:var(--s-4);">${item.price_coins || 0} ${emoji('coin', '🪙', 16)}</div>
${canAfford ${canAfford
? `<button class="btn btn-primary w-full" id="buy-btn">${t('common.confirm')}</button>` ? `<button class="btn btn-primary w-full" id="buy-btn">${t('common.confirm')}</button>`
: `<p style="color:var(--error);font-size:13px;">رصيد غير كافي</p>` : `<p style="color:var(--error);font-size:13px;">${t('shop.insufficient_balance')}</p>`
} }
<button class="btn btn-secondary w-full" id="cancel-buy" style="margin-top:var(--s-2);">${t('common.cancel')}</button> <button class="btn btn-secondary w-full" id="cancel-buy" style="margin-top:var(--s-2);">${t('common.cancel')}</button>
</div> </div>
...@@ -90,7 +90,7 @@ async function purchasePrompt(el, item) { ...@@ -90,7 +90,7 @@ async function purchasePrompt(el, item) {
await net.post('shop.php', { action: 'purchase', cosmetic_id: item.id }); await net.post('shop.php', { action: 'purchase', cosmetic_id: item.id });
audio.play('coin', 'reward'); audio.play('coin', 'reward');
store.set('player.coins', (player.coins || 0) - (item.price_coins || 0)); store.set('player.coins', (player.coins || 0) - (item.price_coins || 0));
overlay.innerHTML = `<div class="card" style="padding:var(--s-6);text-align:center;"><div style="font-size:36px;margin-bottom:var(--s-2);">${emoji('checkmark', '✅', 36)}</div><div>تم الشراء!</div></div>`; overlay.innerHTML = `<div class="card" style="padding:var(--s-6);text-align:center;"><div style="font-size:36px;margin-bottom:var(--s-2);">${emoji('checkmark', '✅', 36)}</div><div>${t('shop.purchased')}</div></div>`;
setTimeout(() => { overlay.classList.remove('active'); overlay.innerHTML = ''; }, 1500); setTimeout(() => { overlay.classList.remove('active'); overlay.innerHTML = ''; }, 1500);
} catch (e) { } catch (e) {
overlay.innerHTML = `<div class="card" style="padding:var(--s-6);text-align:center;color:var(--error);">${t('common.error')}</div>`; overlay.innerHTML = `<div class="card" style="padding:var(--s-6);text-align:center;color:var(--error);">${t('common.error')}</div>`;
......
...@@ -5,7 +5,7 @@ import { emoji } from '../../../core/theme.js'; ...@@ -5,7 +5,7 @@ import { emoji } from '../../../core/theme.js';
export async function mountActivity(el) { export async function mountActivity(el) {
el.innerHTML = ` el.innerHTML = `
<div style="padding:16px;display:flex;flex-direction:column;gap:12px;"> <div style="padding:16px;display:flex;flex-direction:column;gap:12px;">
<h2 style="font-size:18px;font-weight:700;color:#f8fafc;">📰 آخر الأخبار</h2> <h2 style="font-size:18px;font-weight:700;color:#f8fafc;">📰 ${t('social.activity_tab')}</h2>
<div id="activity-list"></div> <div id="activity-list"></div>
</div> </div>
`; `;
...@@ -14,25 +14,25 @@ export async function mountActivity(el) { ...@@ -14,25 +14,25 @@ export async function mountActivity(el) {
const data = await net.get('activity.php'); const data = await net.get('activity.php');
renderActivity(el, data.activity || []); renderActivity(el, data.activity || []);
} catch (e) { } catch (e) {
el.querySelector('#activity-list').innerHTML = '<div style="text-align:center;color:#64748b;padding:32px;">لا توجد أخبار</div>'; el.querySelector('#activity-list').innerHTML = `<div style="text-align:center;color:#64748b;padding:32px;">${t('social.no_activity_short')}</div>`;
} }
} }
function renderActivity(el, activities) { function renderActivity(el, activities) {
const list = el.querySelector('#activity-list'); const list = el.querySelector('#activity-list');
if (activities.length === 0) { if (activities.length === 0) {
list.innerHTML = '<div style="text-align:center;color:#64748b;padding:32px;">لا توجد أخبار — العب لتظهر أخبارك هنا</div>'; list.innerHTML = `<div style="text-align:center;color:#64748b;padding:32px;">${t('social.no_activity_short')}</div>`;
return; return;
} }
const actionLabels = { const actionLabels = {
'game_win': 'فاز بمباراة', 'game_win': t('social.game_win'),
'game_loss': 'خسر مباراة', 'game_loss': t('social.game_loss'),
'game_draw': 'تعادل في مباراة', 'game_draw': t('social.game_draw'),
'achievement_unlock': 'حصل على إنجاز', 'achievement_unlock': t('social.achievement_unlock'),
'level_up': 'ارتقى لمستوى جديد', 'level_up': t('social.level_up'),
'tournament_join': 'انضم لبطولة', 'tournament_join': t('social.tournament_join'),
'friend_add': 'أضاف صديق جديد' 'friend_add': t('social.friend_add')
}; };
list.innerHTML = activities.map(a => { list.innerHTML = activities.map(a => {
...@@ -58,9 +58,9 @@ function timeAgo(date) { ...@@ -58,9 +58,9 @@ function timeAgo(date) {
if (!date) return ''; if (!date) return '';
const diff = Date.now() - new Date(date).getTime(); const diff = Date.now() - new Date(date).getTime();
const mins = Math.floor(diff / 60000); const mins = Math.floor(diff / 60000);
if (mins < 1) return 'الآن'; if (mins < 1) return t('common.now');
if (mins < 60) return `منذ ${mins} دقيقة`; if (mins < 60) return t('common.minutes_ago', { n: mins });
const hours = Math.floor(mins / 60); const hours = Math.floor(mins / 60);
if (hours < 24) return `منذ ${hours} ساعة`; if (hours < 24) return t('common.hours_ago', { n: hours });
return `منذ ${Math.floor(hours / 24)} يوم`; return t('common.days_ago', { n: Math.floor(hours / 24) });
} }
This diff is collapsed.
This diff is collapsed.
...@@ -21,15 +21,15 @@ export async function mountGroupChat(el, params = {}) { ...@@ -21,15 +21,15 @@ export async function mountGroupChat(el, params = {}) {
<div style="padding:10px 16px;background:#0f0f1e;display:flex;align-items:center;gap:10px;border-bottom:1px solid rgba(255,255,255,0.06);"> <div style="padding:10px 16px;background:#0f0f1e;display:flex;align-items:center;gap:10px;border-bottom:1px solid rgba(255,255,255,0.06);">
<button class="btn btn-secondary" id="back-btn" style="width:34px;height:34px;padding:0;font-size:14px;">←</button> <button class="btn btn-secondary" id="back-btn" style="width:34px;height:34px;padding:0;font-size:14px;">←</button>
<div id="group-header" style="flex:1;cursor:pointer;"> <div id="group-header" style="flex:1;cursor:pointer;">
<div style="font-size:15px;font-weight:600;color:#f8fafc;">جاري التحميل...</div> <div style="font-size:15px;font-weight:600;color:#f8fafc;">${t('common.loading')}</div>
</div> </div>
<button class="btn btn-secondary" id="invite-game-btn" style="min-height:32px;padding:5px 10px;font-size:12px;">${emoji('swords', '⚔️', 13)} لعب</button> <button class="btn btn-secondary" id="invite-game-btn" style="min-height:32px;padding:5px 10px;font-size:12px;">${emoji('swords', '⚔️', 13)} ${t('group.play')}</button>
</div> </div>
<div id="invite-banner" style="display:none;"></div> <div id="invite-banner" style="display:none;"></div>
<div id="chat-messages" style="flex:1;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:6px;"></div> <div id="chat-messages" style="flex:1;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:6px;"></div>
<div style="padding:10px 16px;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.06);display:flex;gap:8px;align-items:center;"> <div style="padding:10px 16px;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.06);display:flex;gap:8px;align-items:center;">
<input class="input" id="msg-input" type="text" placeholder="اكتب رسالة..." style="flex:1;min-height:38px;" maxlength="500"> <input class="input" id="msg-input" type="text" placeholder="${t('chat.placeholder')}" style="flex:1;min-height:38px;" maxlength="500">
<button class="btn btn-primary" id="send-btn" style="min-height:38px;padding:0 14px;">إرسال</button> <button class="btn btn-primary" id="send-btn" style="min-height:38px;padding:0 14px;">${t('common.send')}</button>
</div> </div>
</div> </div>
`; `;
...@@ -55,14 +55,14 @@ export async function mountGroupChat(el, params = {}) { ...@@ -55,14 +55,14 @@ export async function mountGroupChat(el, params = {}) {
const header = el.querySelector('#group-header'); const header = el.querySelector('#group-header');
header.innerHTML = ` header.innerHTML = `
<div style="font-size:15px;font-weight:600;color:#f8fafc;">${escapeHtml(groupData.name)}</div> <div style="font-size:15px;font-weight:600;color:#f8fafc;">${escapeHtml(groupData.name)}</div>
<div style="font-size:11px;color:#64748b;">${members.length} أعضاء</div> <div style="font-size:11px;color:#64748b;">${members.length} ${t('group.members')}</div>
`; `;
header.addEventListener('click', () => { header.addEventListener('click', () => {
audio.play('click'); audio.play('click');
scene.push('group-members', { groupId, myRole: detail.my_role }); scene.push('group-members', { groupId, myRole: detail.my_role });
}); });
} catch (e) { } catch (e) {
el.querySelector('#group-header').innerHTML = `<div style="color:var(--error);">خطأ في تحميل المجموعة</div>`; el.querySelector('#group-header').innerHTML = `<div style="color:var(--error);">${t('group.load_error')}</div>`;
return; return;
} }
...@@ -138,8 +138,8 @@ function messageHtml(msg, myId, memberMap) { ...@@ -138,8 +138,8 @@ function messageHtml(msg, myId, memberMap) {
const meta = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata) : (msg.metadata || {}); const meta = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata) : (msg.metadata || {});
let systemText = msg.content; let systemText = msg.content;
if (msg.content === '__system:game_invite') { if (msg.content === '__system:game_invite') {
const gameLabel = meta.game_key === 'ludo' ? 'لودو' : meta.game_key === 'domino' ? 'دومينو' : 'شطرنج'; const gameLabel = meta.game_key === 'ludo' ? t('game.ludo') : meta.game_key === 'domino' ? t('game.domino') : t('game.chess');
systemText = `${emoji('swords', '⚔️', 12)} دعوة لعب ${gameLabel} (${meta.required_players} لاعبين)`; systemText = `${emoji('swords', '⚔️', 12)} ${t('group.play_invite')} ${gameLabel} (${meta.required_players} ${t('group.players')})`;
} }
return `<div style="text-align:center;padding:6px;font-size:11px;color:#64748b;">${systemText}</div>`; return `<div style="text-align:center;padding:6px;font-size:11px;color:#64748b;">${systemText}</div>`;
} }
...@@ -187,7 +187,7 @@ async function checkGroupInvites(el, groupId, myId) { ...@@ -187,7 +187,7 @@ async function checkGroupInvites(el, groupId, myId) {
banner.style.display = 'block'; banner.style.display = 'block';
banner.innerHTML = invites.map(inv => { banner.innerHTML = invites.map(inv => {
const gameLabel = inv.game_key === 'ludo' ? 'لودو' : inv.game_key === 'domino' ? 'دومينو' : 'شطرنج'; const gameLabel = inv.game_key === 'ludo' ? t('game.ludo') : inv.game_key === 'domino' ? t('game.domino') : t('game.chess');
const accepted = inv.accepted || []; const accepted = inv.accepted || [];
const isAccepted = accepted.includes(myId); const isAccepted = accepted.includes(myId);
const isInviter = accepted[0] === myId; const isInviter = accepted[0] === myId;
...@@ -197,7 +197,7 @@ async function checkGroupInvites(el, groupId, myId) { ...@@ -197,7 +197,7 @@ async function checkGroupInvites(el, groupId, myId) {
<div style="flex:1;"> <div style="flex:1;">
<div style="font-size:12px;font-weight:600;color:#34D399;">${gameLabel}${accepted.length}/${inv.required_players}</div> <div style="font-size:12px;font-weight:600;color:#34D399;">${gameLabel}${accepted.length}/${inv.required_players}</div>
</div> </div>
${isAccepted || isInviter ? `<span style="font-size:11px;color:#64748b;">✓ قبلت</span>` : `<button class="btn btn-primary accept-invite-btn" data-match="${inv.match_id}" data-game="${inv.game_key}" style="min-height:28px;padding:4px 12px;font-size:11px;">انضم</button>`} ${isAccepted || isInviter ? `<span style="font-size:11px;color:#64748b;">${t('group.accepted')}</span>` : `<button class="btn btn-primary accept-invite-btn" data-match="${inv.match_id}" data-game="${inv.game_key}" style="min-height:28px;padding:4px 12px;font-size:11px;">${t('group.join')}</button>`}
</div>`; </div>`;
}).join(''); }).join('');
...@@ -219,12 +219,12 @@ async function checkGroupInvites(el, groupId, myId) { ...@@ -219,12 +219,12 @@ async function checkGroupInvites(el, groupId, myId) {
scene.push('chess-game', { matchId: res.match_id, color: res.color }); scene.push('chess-game', { matchId: res.match_id, color: res.color });
} }
} else { } else {
btn.textContent = '✓ قبلت'; btn.textContent = t('group.accepted');
btn.style.background = 'transparent'; btn.style.background = 'transparent';
btn.style.color = '#64748b'; btn.style.color = '#64748b';
} }
} catch (e) { } catch (e) {
btn.textContent = 'خطأ'; btn.textContent = t('social.error');
btn.disabled = false; btn.disabled = false;
} }
}); });
...@@ -241,26 +241,26 @@ function showGamePicker(el, groupId) { ...@@ -241,26 +241,26 @@ function showGamePicker(el, groupId) {
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:9999;display:flex;align-items:flex-end;justify-content:center;'; overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:9999;display:flex;align-items:flex-end;justify-content:center;';
overlay.innerHTML = ` overlay.innerHTML = `
<div style="background:#1a1a2e;border-radius:20px 20px 0 0;padding:24px;width:100%;max-width:400px;display:flex;flex-direction:column;gap:12px;"> <div style="background:#1a1a2e;border-radius:20px 20px 0 0;padding:24px;width:100%;max-width:400px;display:flex;flex-direction:column;gap:12px;">
<div style="font-size:16px;font-weight:700;color:#f8fafc;text-align:center;">دعوة لعب في المجموعة</div> <div style="font-size:16px;font-weight:700;color:#f8fafc;text-align:center;">${t('group.play_invite')}</div>
<button class="game-pick" data-game="chess" data-players="2" style="display:flex;align-items:center;gap:12px;padding:14px;background:#2a2a4a;border-radius:12px;border:none;cursor:pointer;width:100%;"> <button class="game-pick" data-game="chess" data-players="2" style="display:flex;align-items:center;gap:12px;padding:14px;background:#2a2a4a;border-radius:12px;border:none;cursor:pointer;width:100%;">
<span style="font-size:24px;">♟️</span> <span style="font-size:24px;">♟️</span>
<div style="text-align:right;flex:1;"> <div style="text-align:right;flex:1;">
<div style="font-size:14px;font-weight:600;color:#f8fafc;">شطرنج</div> <div style="font-size:14px;font-weight:600;color:#f8fafc;">${t('game.chess')}</div>
<div style="font-size:11px;color:#64748b;">لاعبين</div> <div style="font-size:11px;color:#64748b;">${t('group.players')}</div>
</div> </div>
</button> </button>
<button class="game-pick" data-game="ludo" data-players="4" style="display:flex;align-items:center;gap:12px;padding:14px;background:#2a2a4a;border-radius:12px;border:none;cursor:pointer;width:100%;"> <button class="game-pick" data-game="ludo" data-players="4" style="display:flex;align-items:center;gap:12px;padding:14px;background:#2a2a4a;border-radius:12px;border:none;cursor:pointer;width:100%;">
<span style="font-size:24px;">🎲</span> <span style="font-size:24px;">🎲</span>
<div style="text-align:right;flex:1;"> <div style="text-align:right;flex:1;">
<div style="font-size:14px;font-weight:600;color:#f8fafc;">لودو</div> <div style="font-size:14px;font-weight:600;color:#f8fafc;">${t('game.ludo')}</div>
<div style="font-size:11px;color:#64748b;">2-4 لاعبين</div> <div style="font-size:11px;color:#64748b;">2-4 ${t('group.players')}</div>
</div> </div>
</button> </button>
<button class="game-pick" data-game="domino" data-players="2" style="display:flex;align-items:center;gap:12px;padding:14px;background:#2a2a4a;border-radius:12px;border:none;cursor:pointer;width:100%;"> <button class="game-pick" data-game="domino" data-players="2" style="display:flex;align-items:center;gap:12px;padding:14px;background:#2a2a4a;border-radius:12px;border:none;cursor:pointer;width:100%;">
<span style="font-size:24px;">🁣</span> <span style="font-size:24px;">🁣</span>
<div style="text-align:right;flex:1;"> <div style="text-align:right;flex:1;">
<div style="font-size:14px;font-weight:600;color:#f8fafc;">دومينو</div> <div style="font-size:14px;font-weight:600;color:#f8fafc;">${t('game.domino')}</div>
<div style="font-size:11px;color:#64748b;">لاعبين</div> <div style="font-size:11px;color:#64748b;">${t('group.players')}</div>
</div> </div>
</button> </button>
<button id="cancel-pick" class="btn btn-secondary w-full" style="min-height:40px;">${t('common.cancel')}</button> <button id="cancel-pick" class="btn btn-secondary w-full" style="min-height:40px;">${t('common.cancel')}</button>
...@@ -282,7 +282,7 @@ function showGamePicker(el, groupId) { ...@@ -282,7 +282,7 @@ function showGamePicker(el, groupId) {
game_key: gameKey, game_key: gameKey,
required_players: requiredPlayers required_players: requiredPlayers
}); });
bus.emit('toast', { text: 'تم إرسال دعوة اللعب!', type: 'success' }); bus.emit('toast', { text: t('group.invite_sent'), type: 'success' });
} catch (e) { } catch (e) {
bus.emit('toast', { text: e.message || t('common.error'), type: 'error' }); bus.emit('toast', { text: e.message || t('common.error'), type: 'error' });
} }
......
...@@ -10,22 +10,22 @@ export async function mountGroupCreate(el) { ...@@ -10,22 +10,22 @@ export async function mountGroupCreate(el) {
<div style="display:flex;flex-direction:column;height:100%;"> <div style="display:flex;flex-direction:column;height:100%;">
<div style="padding:12px 16px;background:#0f0f1e;display:flex;align-items:center;gap:12px;"> <div style="padding:12px 16px;background:#0f0f1e;display:flex;align-items:center;gap:12px;">
<button class="btn btn-secondary" id="back-btn" style="width:36px;height:36px;padding:0;">←</button> <button class="btn btn-secondary" id="back-btn" style="width:36px;height:36px;padding:0;">←</button>
<h2 style="font-size:18px;font-weight:700;color:#f8fafc;">إنشاء مجموعة</h2> <h2 style="font-size:18px;font-weight:700;color:#f8fafc;">${t('group.create_title')}</h2>
</div> </div>
<div style="flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px;"> <div style="flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:16px;">
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);display:block;margin-bottom:6px;">اسم المجموعة</label> <label style="font-size:13px;color:var(--text-secondary);display:block;margin-bottom:6px;">${t('group.name_label')}</label>
<input class="input" id="group-name" type="text" placeholder="اسم المجموعة..." maxlength="50" style="width:100%;"> <input class="input" id="group-name" type="text" placeholder="${t('group.name_label')}..." maxlength="50" style="width:100%;">
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);display:block;margin-bottom:6px;">اختر أصدقاء للإضافة</label> <label style="font-size:13px;color:var(--text-secondary);display:block;margin-bottom:6px;">${t('group.select_friends')}</label>
<div id="friends-picker" style="display:flex;flex-direction:column;gap:6px;"> <div id="friends-picker" style="display:flex;flex-direction:column;gap:6px;">
<div style="text-align:center;padding:12px;color:var(--text-secondary);font-size:13px;">${t('common.loading')}</div> <div style="text-align:center;padding:12px;color:var(--text-secondary);font-size:13px;">${t('common.loading')}</div>
</div> </div>
</div> </div>
<button class="btn btn-primary w-full" id="create-btn" style="min-height:48px;font-size:15px;font-weight:700;">إنشاء المجموعة</button> <button class="btn btn-primary w-full" id="create-btn" style="min-height:48px;font-size:15px;font-weight:700;">${t('group.create_btn')}</button>
</div> </div>
</div> </div>
`; `;
...@@ -41,7 +41,7 @@ export async function mountGroupCreate(el) { ...@@ -41,7 +41,7 @@ export async function mountGroupCreate(el) {
const picker = el.querySelector('#friends-picker'); const picker = el.querySelector('#friends-picker');
if (!friends.length) { if (!friends.length) {
picker.innerHTML = `<div style="text-align:center;padding:12px;color:var(--text-secondary);font-size:13px;">لا يوجد أصدقاء</div>`; picker.innerHTML = `<div style="text-align:center;padding:12px;color:var(--text-secondary);font-size:13px;">${t('social.no_friends')}</div>`;
} else { } else {
picker.innerHTML = friends.map(f => ` picker.innerHTML = friends.map(f => `
<label data-id="${f.id}" style="display:flex;align-items:center;gap:10px;padding:10px;background:#1a1a2e;border-radius:10px;cursor:pointer;transition:background 0.1s;"> <label data-id="${f.id}" style="display:flex;align-items:center;gap:10px;padding:10px;background:#1a1a2e;border-radius:10px;cursor:pointer;transition:background 0.1s;">
...@@ -67,13 +67,13 @@ export async function mountGroupCreate(el) { ...@@ -67,13 +67,13 @@ export async function mountGroupCreate(el) {
el.querySelector('#create-btn').addEventListener('click', async () => { el.querySelector('#create-btn').addEventListener('click', async () => {
const name = el.querySelector('#group-name').value.trim(); const name = el.querySelector('#group-name').value.trim();
if (!name || name.length < 2) { if (!name || name.length < 2) {
bus.emit('toast', { text: 'أدخل اسم المجموعة (حرفين على الأقل)', type: 'error' }); bus.emit('toast', { text: t('group.name_min'), type: 'error' });
return; return;
} }
const btn = el.querySelector('#create-btn'); const btn = el.querySelector('#create-btn');
btn.disabled = true; btn.disabled = true;
btn.textContent = 'جاري الإنشاء...'; btn.textContent = t('group.creating');
try { try {
const res = await net.post('groups.php', { const res = await net.post('groups.php', {
...@@ -83,13 +83,13 @@ export async function mountGroupCreate(el) { ...@@ -83,13 +83,13 @@ export async function mountGroupCreate(el) {
}); });
if (res.error) throw new Error(res.error); if (res.error) throw new Error(res.error);
audio.play('click'); audio.play('click');
bus.emit('toast', { text: 'تم إنشاء المجموعة!', type: 'success' }); bus.emit('toast', { text: t('group.created'), type: 'success' });
scene.pop(); scene.pop();
scene.push('group-chat', { groupId: res.group?.id }); scene.push('group-chat', { groupId: res.group?.id });
} catch (e) { } catch (e) {
bus.emit('toast', { text: e.message || t('common.error'), type: 'error' }); bus.emit('toast', { text: e.message || t('common.error'), type: 'error' });
btn.disabled = false; btn.disabled = false;
btn.textContent = 'إنشاء المجموعة'; btn.textContent = t('group.create_btn');
} }
}); });
} }
......
This diff is collapsed.
...@@ -32,7 +32,7 @@ function renderNotifications(el, notifications) { ...@@ -32,7 +32,7 @@ function renderNotifications(el, notifications) {
} }
const hasUnread = notifications.some(n => !n.is_read); const hasUnread = notifications.some(n => !n.is_read);
list.innerHTML = (hasUnread ? `<button id="mark-all-read" style="margin-bottom:8px;background:none;border:1px solid rgba(255,255,255,0.1);color:#94a3b8;font-size:12px;padding:6px 14px;border-radius:8px;cursor:pointer;font-family:inherit;">تعليم الكل كمقروء</button>` : '') + list.innerHTML = (hasUnread ? `<button id="mark-all-read" style="margin-bottom:8px;background:none;border:1px solid rgba(255,255,255,0.1);color:#94a3b8;font-size:12px;padding:6px 14px;border-radius:8px;cursor:pointer;font-family:inherit;">${t('chat.mark_all_read')}</button>` : '') +
notifications.map(n => ` notifications.map(n => `
<div class="card notif-item" data-id="${n.id}" style="padding:var(--s-3);margin-bottom:var(--s-2);opacity:${n.is_read ? '0.7' : '1'};cursor:pointer;"> <div class="card notif-item" data-id="${n.id}" style="padding:var(--s-3);margin-bottom:var(--s-2);opacity:${n.is_read ? '0.7' : '1'};cursor:pointer;">
<div style="font-size:14px;font-weight:${n.is_read ? '400' : '600'};">${n.title_ar || n.title || ''}</div> <div style="font-size:14px;font-weight:${n.is_read ? '400' : '600'};">${n.title_ar || n.title || ''}</div>
...@@ -71,10 +71,10 @@ function timeAgo(date) { ...@@ -71,10 +71,10 @@ function timeAgo(date) {
if (!date) return ''; if (!date) return '';
const diff = Date.now() - new Date(date).getTime(); const diff = Date.now() - new Date(date).getTime();
const mins = Math.floor(diff / 60000); const mins = Math.floor(diff / 60000);
if (mins < 1) return 'الآن'; if (mins < 1) return t('common.now');
if (mins < 60) return `منذ ${mins} دقيقة`; if (mins < 60) return t('common.minutes_ago', { n: mins });
const hours = Math.floor(mins / 60); const hours = Math.floor(mins / 60);
if (hours < 24) return `منذ ${hours} ساعة`; if (hours < 24) return t('common.hours_ago', { n: hours });
const days = Math.floor(hours / 24); const days = Math.floor(hours / 24);
return `منذ ${days} يوم`; return t('common.days_ago', { n: days });
} }
This diff is collapsed.
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