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
...@@ -126,7 +126,547 @@ const strings = { ...@@ -126,7 +126,547 @@ const strings = {
'achievements.gameplay': 'اللعب', 'achievements.gameplay': 'اللعب',
'achievements.social': 'اجتماعي', 'achievements.social': 'اجتماعي',
'achievements.progression': 'التقدم', 'achievements.progression': 'التقدم',
'achievements.collection': 'جمع' 'achievements.collection': 'جمع',
'common.ok': 'حسناً',
'common.you': 'أنت',
'common.opponent': 'خصم',
'common.player': 'لاعب',
'common.friend': 'صديق',
'common.back': 'رجوع',
'common.send': 'إرسال',
'common.sending': 'جاري الإرسال...',
'common.sent': '✓ تم الإرسال',
'common.failed': 'فشل',
'common.already_sent': 'مرسل سابقاً',
'common.already_friends': 'صديق بالفعل',
'common.accepted': '✓ تم القبول',
'common.done': '✓ تم',
'common.error_load': 'فشل التحميل',
'common.now': 'الآن',
'common.minutes_ago': 'منذ {n} دقيقة',
'common.hours_ago': 'منذ {n} ساعة',
'common.days_ago': 'منذ {n} يوم',
'common.online': 'متصل الآن',
'common.offline': 'غير متصل',
'common.level': 'مستوى {n}',
'common.pieces': '{n} قطع',
'game.round_won': 'فزت بالجولة!',
'game.round_lost': 'خسرت الجولة',
'game.round_end': 'انتهت الجولة',
'game.round_next': 'الجولة التالية تبدأ...',
'game.points': '+{n} نقطة',
'game.choose_placement': 'اختر مكان الوضع',
'game.opponent_plays': 'الخصم يلعب...',
'game.one_piece_warning': '⚠️ قطعة واحدة!',
'game.two_piece_warning': '⚠️ قطعتين!',
'game.resign_confirm': 'هل تريد الاستسلام؟',
'game.resign_yes': 'نعم، استسلم',
'game.resign_no': 'تراجع',
'game.place_first': 'ضع أول قطعة',
'game.vs_bot': 'ضد البوت',
'game.online': 'أونلاين',
'game.vs_friend': 'تحدي صديق',
'game.roll_dice': 'ارمِ النرد',
'game.choose_piece': 'اختر قطعة',
'game.speed_mode': '⚡ وضع السرعة!',
'game.turn_of': 'دور {name}',
'game.bot_thinking': '{name} يفكر...',
'game.rating': 'التصنيف',
'game.coins': 'عملات',
'game.xp': 'خبرة',
'game.moves_count': '{n} نقلة',
'game.rounds_count': '{n} جولة',
'game.rounds_count_plural': '{n} جولات',
'game.checkmate': 'كش ملك',
'game.stalemate': 'بات (جمود)',
'game.timeout': 'انتهاء الوقت',
'game.threefold': 'تكرار ثلاثي',
'game.insufficient': 'قطع غير كافية',
'game.win': 'فوز!',
'game.loss': 'خسارة',
'game.draw_game': 'تعادل',
'game.resigned': 'استسلمت',
'game.opponent_resigned': 'الخصم استسلم!',
'game.opponent_abandoned': 'الخصم انقطع',
'game.congrats': 'مبروك! أنت البطل',
'game.second_place': 'المركز الثاني — أحسنت!',
'game.third_place': 'المركز الثالث',
'game.fourth_place': 'المركز الرابع',
'game.withdrew': 'انسحبت من المباراة',
'game.player_ranking': 'ترتيب اللاعبين',
'game.play_again': 'العب مرة ثانية',
'game.back_to_menu': 'رجوع للقائمة',
'game.not_found': 'مباراة غير موجودة',
'match.disconnected_title': 'الخصم انقطع اتصاله',
'match.disconnected_waiting': 'في انتظار عودته...',
'match.disconnected_auto_win': 'ستفوز تلقائياً إذا لم يعد خلال الوقت المحدد',
'match.claim_early_win': 'فوز مبكر',
'match.opponent_returned': '✅ الخصم عاد — استمروا!',
'match.you_won': 'فزت!',
'match.opponent_left': 'الخصم غادر المباراة',
'match.return': 'العودة',
'match.connection_lost': 'انقطع الاتصال',
'match.reconnecting': 'جاري إعادة الاتصال...',
'match.reconnected': '✅ تم استعادة الاتصال',
'match.restoring': 'جاري استعادة المباراة...',
'match.your_turn_warning': '⏱️ دورك! العب قبل انتهاء الوقت',
'match.opponent_thinking': 'الخصم يفكر...',
'match.bot_replacement': '🤖 الخصم غادر — بوت يكمل بدلاً منه',
'match.your_time_expired': '⏱️ انتهى وقتك! تم التمرير تلقائياً',
'match.opponent_time_expired': '⏱️ انتهى وقت الخصم — دورك!',
'tournament.round_ready': 'جولة {n} جاهزة!',
'tournament.tap_to_play': 'اضغط للعب مباراتك',
'tournament.play': 'العب',
'tournament.back_to': 'العودة للبطولة',
'mp.profile': 'الملف الشخصي',
'mp.add_friend': 'إضافة صديق',
'mp.report': 'إبلاغ',
'mp.reported': '✓ تم الإبلاغ',
'mp.block': 'حظر',
'social.friends_tab': 'الأصدقاء',
'social.pending_tab': 'الطلبات',
'social.online_tab': 'متصلين',
'social.groups_tab': 'المجموعات',
'social.activity_tab': 'أخبار',
'social.search_btn': 'بحث',
'social.search_placeholder': 'اسم المستخدم أو الاسم...',
'social.search_hint': 'ابحث بأي جزء من الاسم (حرفين على الأقل)',
'social.search_min_chars': 'أدخل حرفين على الأقل',
'social.searching': 'جاري البحث...',
'social.no_results': 'لم يتم العثور على نتائج — جرب اسم آخر',
'social.search_failed': 'فشل البحث',
'social.add_btn': '+ أضف',
'social.no_friends': 'لا يوجد أصدقاء بعد',
'social.no_friends_hint': 'ابحث عن لاعبين وأرسل لهم طلب صداقة',
'social.search_players': 'ابحث عن لاعبين',
'social.friends_count': '{n} أصدقاء',
'social.online_count': '{n} متصل',
'social.no_pending': 'لا توجد طلبات معلقة',
'social.pending_count': '{n} طلب معلق',
'social.sent_friend_request': 'أرسل لك طلب صداقة',
'social.no_online_friends': 'لا يوجد أصدقاء متصلين الآن',
'social.will_appear_online': 'سيظهرون هنا عند دخولهم',
'social.online_now_count': '{n} متصل الآن',
'social.remove_friend': 'إزالة صديق',
'social.remove_confirm': 'إزالة {name} من الأصدقاء؟',
'social.challenges_you': '{name} يتحداك!',
'social.accept': 'قبول',
'social.error': 'خطأ',
'social.retry_load': 'حاول مرة أخرى',
'social.no_activity': 'لا توجد أخبار — العب لتظهر أخبارك هنا',
'social.no_activity_short': 'لا توجد أخبار',
'social.game_win': 'فاز بمباراة',
'social.game_loss': 'خسر مباراة',
'social.game_draw': 'تعادل',
'social.achievement_unlock': 'حصل على إنجاز',
'social.level_up': 'ارتقى لمستوى جديد',
'social.tournament_join': 'انضم لبطولة',
'social.tournament_win': 'فاز ببطولة',
'social.friend_add': 'أضاف صديقاً جديداً',
'challenge.title': 'تحدّي صديق',
'challenge.loading_friends': 'جاري تحميل الأصدقاء...',
'challenge.no_friends': 'لا يوجد أصدقاء بعد',
'challenge.no_friends_hint': 'أضف أصدقاء أولاً لتتمكن من تحديهم',
'challenge.online_now': 'متصلين الآن ({n})',
'challenge.offline': 'غير متصلين',
'challenge.select_game': 'اختر اللعبة ونوع الوقت',
'challenge.send': 'أرسل التحدي',
'challenge.sending': '⏳ جاري الإرسال...',
'challenge.sent': '✓ تم إرسال التحدي!',
'challenge.failed_create': 'فشل إنشاء المباراة',
'challenge.failed_retry': 'فشل — حاول مرة أخرى',
'challenge.sent_msg': 'أرسل تحدي {game}',
'chat.placeholder': 'اكتب رسالة...',
'chat.loading_messages': 'جاري التحميل...',
'chat.failed_load': 'فشل تحميل الرسائل',
'chat.start_conversation': 'ابدأ المحادثة مع صديقك!',
'chat.challenge_label': 'تحدي {game}',
'chat.challenge_title': 'تحدّي {name}',
'chat.choose_game': 'اختر اللعبة',
'lobby.title': 'غرفة التحدي',
'lobby.ready': 'جاهز',
'lobby.waiting': 'في الانتظار...',
'lobby.waiting_accept': 'في انتظار القبول...',
'lobby.ready_start': 'جاهز للبدء!',
'lobby.waiting_opponent': 'في انتظار الخصم...',
'lobby.auto_start': 'سيتم بدء المباراة تلقائياً عند قبول التحدي',
'lobby.start': 'ابدأ المباراة',
'lobby.accepted_preparing': 'تم القبول! جاري تجهيز المباراة...',
'lobby.opponent_accepted': 'الخصم قبل! جاري البدء...',
'time.bullet': 'رصاصة',
'time.blitz': 'خاطفة',
'time.rapid': 'سريعة',
'time.classical': 'كلاسيكية',
'time.1min': '1 دقيقة',
'time.3min': '3 دقائق',
'time.5min': '5 دقائق',
'time.10min': '10 دقائق',
'time.15min': '15 دقيقة',
'time.20min': '20 دقيقة',
'time.30min': '30 دقيقة',
'time.60min': '60 دقيقة',
'play.single_player': 'لاعب واحد',
'play.single_player_desc': 'العب ضد الكمبيوتر',
'play.online_desc': 'نافس لاعبين حقيقيين',
'play.achievements': 'إنجازات',
'play.gift': 'هدية',
'play.coming_soon': 'قريباً',
'play.login_required_online': 'سجّل دخولك للعب أونلاين',
'play.login_required_friend': 'سجّل دخولك لتحدي صديق',
'domino.title': 'دومينو',
'domino.subtitle': 'أول من يوصل الهدف يفوز!',
'domino.draw_boneyard': 'سحب من المخزن',
'domino.pass': 'تمرير',
'domino.select_bot': 'اختر مستوى البوت',
'domino.bot_strategy': 'كل مستوى له استراتيجية مختلفة',
'domino.beginner': 'مبتدئ',
'domino.beginner_desc': 'يلعب عشوائي',
'domino.intermediate': 'متوسط',
'domino.intermediate_desc': 'يفضل النقاط العالية',
'domino.expert': 'خبير',
'domino.expert_desc': 'استراتيجي ومخادع',
'domino.select_target': 'اختر الهدف',
'domino.target_hint': 'أول من يوصل النقاط يفوز بالمباراة',
'domino.50_points': '50 نقطة',
'domino.50_desc': 'مباراة سريعة (~5 دقائق)',
'domino.100_points': '100 نقطة',
'domino.100_desc': 'كلاسيك (~10 دقائق)',
'domino.150_points': '150 نقطة',
'domino.150_desc': 'مباراة طويلة (~15 دقيقة)',
'ludo.title': 'لودو',
'ludo.subtitle': 'أول من يوصّل كل قطعه يفوز!',
'ludo.local_play': 'لعب محلي',
'ludo.local_desc': 'اختر عدد اللاعبين والبوتات',
'ludo.online_desc': 'العب ضد لاعبين حقيقيين',
'ludo.friend_desc': 'ادعُ أصدقاءك للعب',
'ludo.settings_online': 'إعدادات الأونلاين',
'ludo.settings_play': 'إعدادات اللعب',
'ludo.settings_online_hint': 'اختر عدد اللاعبين الحقيقيين',
'ludo.settings_play_hint': 'اختر تشكيلة اللاعبين',
'ludo.player_count': 'عدد اللاعبين',
'ludo.classic': 'كلاسيك',
'ludo.triple': 'ثلاثي',
'ludo.duel': 'مبارزة',
'ludo.player_layout': 'توزيعة اللاعبين',
'ludo.bot_level': 'مستوى البوت',
'ludo.easy': 'سهل',
'ludo.medium': 'متوسط',
'ludo.hard': 'صعب',
'ludo.seat_order': 'ترتيب المقاعد',
'ludo.search_match': 'ابحث عن مباراة',
'ludo.start_play': 'ابدأ اللعب',
'ludo.players_no_bot': '{n} لاعبين (بدون بوت)',
'ludo.one_player_bots': 'لاعب واحد + {n} بوت',
'ludo.players_bots': '{h} لاعبين + {n} بوت',
'ludo.red': 'أحمر',
'ludo.green': 'أخضر',
'ludo.yellow': 'أصفر',
'ludo.blue': 'أزرق',
'ludo.searching': 'جاري البحث...',
'ludo.searching_hint': 'بنوصّلك بلاعبين قريب',
'ludo.place_first': 'الأول',
'ludo.place_second': 'الثاني',
'ludo.place_third': 'الثالث',
'ludo.place_fourth': 'الرابع',
'backgammon.title': 'طاولة',
'backgammon.subtitle': 'اول من يطلّع كل قطعه يفوز!',
'backgammon.bot_desc': 'اختر المستوى والنوع',
'backgammon.online_desc': 'العب ضد لاعبين حقيقيين',
'backgammon.friend_desc': 'ادعُ صديقك للعب',
'backgammon.settings_online': 'إعدادات الأونلاين',
'backgammon.settings_game': 'إعدادات اللعبة',
'backgammon.game_type': 'نوع اللعبة',
'backgammon.sheshbesh': 'شيش بيش',
'backgammon.mahbousa': 'محبوسة',
'backgammon.thirtyone': '٣١',
'backgammon.match_length': 'طول الماتش',
'backgammon.short_match': 'سريع',
'backgammon.medium_match': 'قصير',
'backgammon.normal_match': 'عادي',
'backgammon.long_match': 'طويل',
'backgammon.doubling_cube': 'مكعب المضاعفة',
'backgammon.enabled': 'مفعّل',
'backgammon.disabled': 'بدون',
'backgammon.searching': 'جاري البحث...',
'backgammon.searching_hint': 'بنوصّلك بلاعب قريب',
'backgammon.win_title': 'مبروك! فزت بالماتش!',
'backgammon.lose_title': 'خسرت هالمرة...',
'backgammon.win_subtitle': 'أداء ممتاز!',
'backgammon.lose_subtitle': 'حاول مرة ثانية!',
'backgammon.abandon_subtitle': 'الخصم انسحب من اللعبة',
'backgammon.from_match': 'من {n}',
'backgammon.opponent_doubles': 'الخصم يضاعف!',
'backgammon.decline': 'رفض',
'backgammon.accept': 'قبول',
'backgammon.no_moves': 'لا حركات متاحة!',
'backgammon.bot_accepted': 'البوت قبل! ×{n}',
'backgammon.bot_declined': 'البوت رفض! فزت بالجولة',
'emote.good_move': 'نقلة ممتازة',
'emote.think': 'يفكر...',
'emote.hurry': 'أسرع',
'emote.wow': 'واو!',
'emote.laugh': 'هههه',
'emote.angry': 'غاضب',
'emote.hello': 'مرحبا',
'emote.nice_game': '!لعبة حلوة',
'emote.great_move': '!حركة ممتازة',
'emote.lucky': '!حظ',
'emote.think_faster': '...فكّر أسرع',
'emote.rematch': '?ريماتش',
'analysis.title': 'تحليل المباراة',
'analysis.analyzing': 'جاري التحليل...',
'analysis.start': 'بداية',
'analysis.engine_analyzing': 'جاري تحليل المحرك...',
'analysis.failed': 'فشل التحليل',
'analysis.openings_book': 'كتاب الافتتاحات',
'review.title': 'مراجعة المباراة',
'review.analyzing_moves': 'جاري تحليل كل نقلة...',
'review.move_classification': 'تصنيف النقلات',
'review.brilliant': '!! رائعة',
'review.great': '! ممتازة',
'review.best': 'أفضل نقلة',
'review.good': '✓ جيدة',
'review.book': 'نظرية',
'review.inaccuracy': '?! عدم دقة',
'review.mistake': '? خطأ',
'review.blunder': '?? خطأ فادح',
'review.full_analysis': 'التحليل التفصيلي (نقلة بنقلة)',
'review.unknown_opening': 'غير معروف',
'spectate.live': 'مشاهدة مباشرة',
'spectate.black': 'أسود',
'spectate.white': 'أبيض',
'spectate.white_wins': 'فاز الأبيض',
'spectate.black_wins': 'فاز الأسود',
'spectate.game_over': 'انتهت المباراة',
'history.no_matches': 'لا توجد مباريات بعد — العب لتظهر هنا',
'history.win': 'فوز',
'history.loss': 'خسارة',
'history.draw': 'تعادل',
'history.insufficient_data': 'بيانات غير كافية',
'history.matches_count': '{n} مباراة',
'result.copied': '✓ تم النسخ',
'result.copy_pgn': 'نسخ',
'result.share_pgn': 'مشاركة PGN',
'result.analyze': 'تحليل المباراة',
'result.rematch': 'إعادة',
'classifier.excellent': 'ممتاز',
'classifier.great': 'جيد جداً',
'classifier.good': 'جيد',
'classifier.average': 'متوسط',
'classifier.poor': 'ضعيف',
'profile.edit_title': 'تعديل الملف الشخصي',
'profile.basic_info': 'المعلومات الأساسية',
'profile.display_name_en': 'الاسم المعروض (إنجليزي)',
'profile.display_name_ar': 'الاسم المعروض (عربي)',
'profile.bio_en': 'النبذة (إنجليزي)',
'profile.bio_ar': 'النبذة (عربي)',
'profile.location': 'الموقع',
'profile.country': 'الدولة',
'profile.select_country': 'اختر الدولة',
'profile.city': 'المدينة',
'profile.preferred_language': 'اللغة المفضلة',
'profile.fide_info': 'معلومات FIDE',
'profile.fide_classical': 'تصنيف FIDE الكلاسيكي',
'profile.fide_rapid': 'تصنيف FIDE السريع',
'profile.fide_blitz': 'تصنيف FIDE الخاطف',
'profile.fide_title': 'لقب FIDE',
'profile.save': 'حفظ التغييرات',
'profile.saving': 'جاري الحفظ...',
'profile.save_success': 'تم حفظ التغييرات بنجاح',
'profile.save_failed': 'فشل حفظ التغييرات',
'profile.uploading': 'جاري الرفع...',
'profile.upload_failed': 'فشل رفع الصورة',
'profile.matches': 'مباريات',
'profile.wins': 'فوز',
'profile.streak': 'سلسلة',
'profile.rating_section': 'التصنيف',
'profile.chess_rapid': 'شطرنج (سريع)',
'profile.chess_blitz': 'شطرنج (خاطف)',
'profile.chess_bullet': 'شطرنج (رصاصة)',
'profile.org_section': 'المنظمة',
'profile.edit_btn': 'تعديل الملف الشخصي',
'profile.org_apply': 'الانضمام لمنظمة',
'profile.logout': 'خروج',
'profile.view_title': 'الملف الشخصي',
'profile.add_friend': '➕ إضافة صديق',
'profile.friend_sent': '✓ تم إرسال الطلب',
'profile.pending_request': 'طلب صداقة معلق',
'profile.challenge': 'تحدي',
'profile.message': 'مراسلة',
'profile.playing_now': 'يلعب الآن — {game}',
'profile.watch': 'شاهد',
'profile.member': 'عضو',
'profile.pending_review': 'قيد المراجعة',
'profile.rejected': 'مرفوض',
'profile.rejection_reason': 'السبب: {reason}',
'profile.membership_request': 'طلب انضمام',
'profile.no_org': 'لست عضواً في أي منظمة بعد',
'org.join_title': 'الانضمام لمنظمة',
'org.no_orgs': 'لا توجد منظمات متاحة حالياً',
'org.apply': 'تقديم طلب',
'org.reapply': 'إعادة التقديم',
'org.apply_title': 'تقديم طلب انضمام — {name}',
'org.doc_type': 'نوع المستند',
'org.notes': 'ملاحظات (اختياري)',
'org.notes_placeholder': 'رسالة لإدارة المنظمة...',
'org.proof_image': 'صورة إثبات',
'org.tap_to_select': 'اضغط لاختيار صورة',
'org.file_limit': 'PNG, JPG, WebP — حد أقصى 5MB',
'org.file_too_large': 'حجم الملف أكبر من 5MB',
'org.submit': 'إرسال الطلب',
'org.submitting': 'جاري الإرسال...',
'org.submit_success': 'تم إرسال الطلب بنجاح',
'org.submit_failed': 'فشل إرسال الطلب',
'room.waiting_friend': 'بانتظار الصديق...',
'room.challenge_from': 'تحدي من {name}',
'room.send_invite': 'أرسل الدعوة لصديقك',
'room.tap_accept': 'اضغط قبول للبدء',
'room.match_created': 'تم إنشاء المباراة، بانتظار القبول...',
'daily.title': 'المكافأة اليومية',
'daily.streak_days': '{n} يوم متتالي',
'daily.day': 'يوم {n}',
'daily.claimed_today': 'تم استلام مكافأة اليوم',
'daily.come_back': 'عد غداً لمكافأة أكبر!',
'daily.today_reward': 'مكافأة اليوم',
'daily.claim_btn': 'استلم المكافأة',
'daily.hint': 'كل يوم تجمع فيه المكافأة يزيد المبلغ • اليوم السابع = 300 عملة!',
'daily.claiming': 'جاري الاستلام...',
'daily.failed': 'فشل — حاول مرة أخرى',
'challenges.title': 'التحديات اليومية',
'challenges.all_done': 'أنجزت كل التحديات اليوم!',
'challenges.streak': '{n} يوم',
'challenges.claim': 'اجمع',
'rank.bronze': 'برونزي',
'rank.silver': 'فضي',
'rank.gold': 'ذهبي',
'rank.platinum': 'بلاتيني',
'rank.diamond': 'ماسي',
'rank.master': 'أسطوري',
'rank.grandmaster': 'جراند ماستر',
'shop.insufficient_balance': 'رصيد غير كافي',
'shop.purchased': 'تم الشراء!',
'group.create': 'إنشاء',
'group.no_groups': 'لا توجد مجموعات بعد',
'group.no_groups_hint': 'أنشئ مجموعة وادعُ أصدقائك!',
'group.members': 'أعضاء',
'group.owner': 'مالك',
'group.admin': 'مشرف',
'group.play': 'لعب',
'group.load_error': 'خطأ في تحميل المجموعة',
'group.play_invite': 'دعوة لعب',
'group.players': 'لاعبين',
'group.join': 'انضم',
'group.accepted': '✓ قبلت',
'group.invite_sent': 'تم إرسال دعوة اللعب!',
'group.create_title': 'إنشاء مجموعة',
'group.name_label': 'اسم المجموعة',
'group.select_friends': 'اختر أصدقاء للإضافة',
'group.create_btn': 'إنشاء المجموعة',
'group.name_min': 'أدخل اسم المجموعة (حرفين على الأقل)',
'group.creating': 'جاري الإنشاء...',
'group.created': 'تم إنشاء المجموعة!',
'group.add': 'إضافة',
'group.leave': 'مغادرة المجموعة',
'group.remove': 'إزالة',
'group.remove_member': 'إزالة عضو',
'group.leave_confirm': 'هل تريد مغادرة المجموعة؟',
'group.leave_btn': 'غادر',
'group.stay': 'ابقَ',
'auth.login_title': 'تسجيل الدخول',
'auth.login_btn': 'دخول',
'auth.register_link': 'سجّل الآن',
'auth.forgot_password': 'نسيت كلمة المرور؟',
'auth.login_failed': 'فشل تسجيل الدخول',
'auth.invalid_email': 'بريد إلكتروني غير صحيح',
'auth.guest_login': 'دخول كضيف',
'auth.register_title': 'إنشاء حساب',
'auth.confirm_password': 'تأكيد كلمة المرور',
'auth.has_account': 'لديك حساب بالفعل؟',
'auth.login_link': 'سجّل دخول',
'auth.register_failed': 'فشل التسجيل',
'auth.password_mismatch': 'كلمتا المرور غير متطابقتين',
'auth.username_short': 'اسم المستخدم قصير جداً',
'auth.password_short': 'كلمة المرور قصيرة (6 أحرف على الأقل)',
'app.tagline': 'العب مع أصدقائك',
'common.or': 'أو',
'common.play_again': 'العب مرة أخرى',
'tournament.title': 'البطولات',
'tournament.upcoming': 'القادمة',
'tournament.active': 'جارية',
'tournament.completed': 'منتهية',
'tournament.no_tournaments': 'لا توجد بطولات حالياً',
'tournament.players': '{n} لاعب',
'tournament.join': 'انضم',
'tournament.joined': 'مسجّل',
'tournament.starts_in': 'تبدأ خلال {n}',
'tournament.entry_fee': 'رسوم الدخول',
'tournament.prize': 'الجائزة',
'tournament.free': 'مجاني',
'tournament.waiting': 'في انتظار اللاعبين...',
'tournament.players_ready': '{n}/{total} لاعب جاهز',
'tournament.starting_soon': 'تبدأ قريباً!',
'tournament.leave': 'مغادرة',
'tournament.ready': 'جاهز',
'tournament.bracket': 'شجرة البطولة',
'tournament.round': 'الجولة {n}',
'tournament.final': 'النهائي',
'tournament.semifinal': 'نصف النهائي',
'tournament.quarterfinal': 'ربع النهائي',
'tournament.bye': 'ترقية مباشرة',
'tournament.match_starting': 'المباراة تبدأ...',
'tournament.opponent': 'الخصم',
'tournament.round_of': 'جولة {n}',
'tournament.champion': 'البطل!',
'tournament.eliminated': 'خسرت',
'tournament.final_rank': 'المركز {n}',
'tournament.prize_won': 'الجائزة: {n} عملة',
'tournament.back_to_lobby': 'العودة',
'tournament.hub_title': 'بطولات مباشرة',
'tournament.create': 'إنشاء بطولة',
'tournament.browse': 'تصفح البطولات',
'tournament.arena': 'أرينا',
'tournament.leave_arena': 'مغادرة الأرينا',
'tournament.no_results': 'لا توجد نتائج بعد',
'tournament.leaderboard': 'المتصدرون',
'tournament.tab_info': 'معلومات',
'tournament.tab_standings': 'الترتيب',
'tournament.tab_rounds': 'الجولات',
'tournament.tab_my_games': 'مبارياتي',
'tournament.tab_bracket': 'الشجرة',
'tournament.tab_arena': 'أرينا',
'tournament.registration_open': 'تسجيل مفتوح',
'tournament.coming_soon': 'قريباً',
'tournament.round_n': 'الجولة {n}/{total}',
'tournament.live_matches': 'مباريات جارية',
'tournament.no_match': 'لا توجد مباراة',
'spectate.watch': 'شاهد',
'chess.rating': 'التصنيف',
'chess.rating_history': 'سجل التصنيف',
'chess.highest': 'الأعلى',
'chess.lowest': 'الأدنى',
'chess.current': 'الحالي',
'puzzle.title': 'أحجية شطرنج',
'puzzle.correct': 'أحسنت! حل صحيح',
'puzzle.wrong': 'حل خاطئ',
'org.members': 'أعضاء',
'org.game': 'اللعبة',
'org.members_section': 'الأعضاء',
'org.join': 'انضم للمنظمة',
'org.joined': 'تم الانضمام',
'org.title': 'المنظمات',
'org.join_btn': 'انضم',
'org.doc_membership': 'بطاقة عضوية',
'org.doc_id': 'بطاقة هوية',
'org.doc_receipt': 'إيصال دفع',
'org.doc_other': 'أخرى',
'ludo.emote_sad': 'حزين',
'ludo.emote_strong': 'قوي',
'ludo.emote_cool': 'كول',
'ludo.phrase_gl': 'حظ سعيد!',
'ludo.phrase_hurry': 'يلا بسرعة!',
'ludo.phrase_nice': 'نايس!',
'ludo.phrase_oops': 'يا ساتر!',
'ludo.phrase_wow': 'ما شاء الله!'
}, },
en: { en: {
'app.name': 'EL3AB', 'app.name': 'EL3AB',
...@@ -253,7 +793,547 @@ const strings = { ...@@ -253,7 +793,547 @@ const strings = {
'achievements.gameplay': 'Gameplay', 'achievements.gameplay': 'Gameplay',
'achievements.social': 'Social', 'achievements.social': 'Social',
'achievements.progression': 'Progression', 'achievements.progression': 'Progression',
'achievements.collection': 'Collection' 'achievements.collection': 'Collection',
'common.ok': 'OK',
'common.you': 'You',
'common.opponent': 'Opponent',
'common.player': 'Player',
'common.friend': 'Friend',
'common.back': 'Back',
'common.send': 'Send',
'common.sending': 'Sending...',
'common.sent': '✓ Sent',
'common.failed': 'Failed',
'common.already_sent': 'Already sent',
'common.already_friends': 'Already friends',
'common.accepted': '✓ Accepted',
'common.done': '✓ Done',
'common.error_load': 'Failed to load',
'common.now': 'Now',
'common.minutes_ago': '{n}m ago',
'common.hours_ago': '{n}h ago',
'common.days_ago': '{n}d ago',
'common.online': 'Online',
'common.offline': 'Offline',
'common.level': 'Level {n}',
'common.pieces': '{n} pieces',
'game.round_won': 'You won the round!',
'game.round_lost': 'You lost the round',
'game.round_end': 'Round over',
'game.round_next': 'Next round starting...',
'game.points': '+{n} points',
'game.choose_placement': 'Choose placement',
'game.opponent_plays': 'Opponent playing...',
'game.one_piece_warning': '⚠️ One piece left!',
'game.two_piece_warning': '⚠️ Two pieces left!',
'game.resign_confirm': 'Do you want to resign?',
'game.resign_yes': 'Yes, resign',
'game.resign_no': 'Cancel',
'game.place_first': 'Place first tile',
'game.vs_bot': 'vs Bot',
'game.online': 'Online',
'game.vs_friend': 'Challenge Friend',
'game.roll_dice': 'Roll Dice',
'game.choose_piece': 'Choose piece',
'game.speed_mode': '⚡ Speed Mode!',
'game.turn_of': "{name}'s turn",
'game.bot_thinking': '{name} thinking...',
'game.rating': 'Rating',
'game.coins': 'Coins',
'game.xp': 'XP',
'game.moves_count': '{n} moves',
'game.rounds_count': '{n} round',
'game.rounds_count_plural': '{n} rounds',
'game.checkmate': 'Checkmate',
'game.stalemate': 'Stalemate',
'game.timeout': 'Timeout',
'game.threefold': 'Threefold Repetition',
'game.insufficient': 'Insufficient Material',
'game.win': 'Victory!',
'game.loss': 'Defeat',
'game.draw_game': 'Draw',
'game.resigned': 'You resigned',
'game.opponent_resigned': 'Opponent resigned!',
'game.opponent_abandoned': 'Opponent disconnected',
'game.congrats': 'Congratulations! You won!',
'game.second_place': '2nd Place — Well played!',
'game.third_place': '3rd Place',
'game.fourth_place': '4th Place',
'game.withdrew': 'You withdrew',
'game.player_ranking': 'Player Rankings',
'game.play_again': 'Play Again',
'game.back_to_menu': 'Back to Menu',
'game.not_found': 'Match not found',
'match.disconnected_title': 'Opponent disconnected',
'match.disconnected_waiting': 'Waiting for them to return...',
'match.disconnected_auto_win': 'You will win automatically if they don\'t return in time',
'match.claim_early_win': 'Claim Win',
'match.opponent_returned': '✅ Opponent returned — continue!',
'match.you_won': 'You Won!',
'match.opponent_left': 'Opponent left the match',
'match.return': 'Return',
'match.connection_lost': 'Connection Lost',
'match.reconnecting': 'Reconnecting...',
'match.reconnected': '✅ Connection restored',
'match.restoring': 'Restoring match...',
'match.your_turn_warning': '⏱️ Your turn! Play before time runs out',
'match.opponent_thinking': 'Opponent thinking...',
'match.bot_replacement': '🤖 Opponent left — bot taking over',
'match.your_time_expired': '⏱️ Time expired! Auto-passed',
'match.opponent_time_expired': '⏱️ Opponent\'s time expired — your turn!',
'tournament.round_ready': 'Round {n} ready!',
'tournament.tap_to_play': 'Tap to play your match',
'tournament.play': 'Play',
'tournament.back_to': 'Back to Tournament',
'mp.profile': 'Profile',
'mp.add_friend': 'Add Friend',
'mp.report': 'Report',
'mp.reported': '✓ Reported',
'mp.block': 'Block',
'social.friends_tab': 'Friends',
'social.pending_tab': 'Requests',
'social.online_tab': 'Online',
'social.groups_tab': 'Groups',
'social.activity_tab': 'News',
'social.search_btn': 'Search',
'social.search_placeholder': 'Username or name...',
'social.search_hint': 'Search by any part of the name (min 2 chars)',
'social.search_min_chars': 'Enter at least 2 characters',
'social.searching': 'Searching...',
'social.no_results': 'No results found — try another name',
'social.search_failed': 'Search failed',
'social.add_btn': '+ Add',
'social.no_friends': 'No friends yet',
'social.no_friends_hint': 'Search for players and send friend requests',
'social.search_players': 'Search for players',
'social.friends_count': '{n} friends',
'social.online_count': '{n} online',
'social.no_pending': 'No pending requests',
'social.pending_count': '{n} pending',
'social.sent_friend_request': 'Sent you a friend request',
'social.no_online_friends': 'No friends online right now',
'social.will_appear_online': 'They\'ll appear here when they log in',
'social.online_now_count': '{n} online now',
'social.remove_friend': 'Remove Friend',
'social.remove_confirm': 'Remove {name} from friends?',
'social.challenges_you': '{name} challenges you!',
'social.accept': 'Accept',
'social.error': 'Error',
'social.retry_load': 'Try again',
'social.no_activity': 'No news — play to see your news here',
'social.no_activity_short': 'No news',
'social.game_win': 'Won a match',
'social.game_loss': 'Lost a match',
'social.game_draw': 'Drew a match',
'social.achievement_unlock': 'Unlocked achievement',
'social.level_up': 'Leveled up',
'social.tournament_join': 'Joined tournament',
'social.tournament_win': 'Won tournament',
'social.friend_add': 'Added a new friend',
'challenge.title': 'Challenge Friend',
'challenge.loading_friends': 'Loading friends...',
'challenge.no_friends': 'No friends yet',
'challenge.no_friends_hint': 'Add friends first to challenge them',
'challenge.online_now': 'Online now ({n})',
'challenge.offline': 'Offline',
'challenge.select_game': 'Choose game and time control',
'challenge.send': 'Send Challenge',
'challenge.sending': '⏳ Sending...',
'challenge.sent': '✓ Challenge sent!',
'challenge.failed_create': 'Failed to create match',
'challenge.failed_retry': 'Failed — try again',
'challenge.sent_msg': 'Sent {game} challenge',
'chat.placeholder': 'Type a message...',
'chat.loading_messages': 'Loading...',
'chat.failed_load': 'Failed to load messages',
'chat.start_conversation': 'Start chatting with your friend!',
'chat.challenge_label': '{game} challenge',
'chat.challenge_title': 'Challenge {name}',
'chat.choose_game': 'Choose game',
'lobby.title': 'Challenge Room',
'lobby.ready': 'Ready',
'lobby.waiting': 'Waiting...',
'lobby.waiting_accept': 'Waiting for acceptance...',
'lobby.ready_start': 'Ready to start!',
'lobby.waiting_opponent': 'Waiting for opponent...',
'lobby.auto_start': 'Match will start automatically when challenge is accepted',
'lobby.start': 'Start Match',
'lobby.accepted_preparing': 'Accepted! Preparing match...',
'lobby.opponent_accepted': 'Opponent accepted! Starting...',
'time.bullet': 'Bullet',
'time.blitz': 'Blitz',
'time.rapid': 'Rapid',
'time.classical': 'Classical',
'time.1min': '1 min',
'time.3min': '3 min',
'time.5min': '5 min',
'time.10min': '10 min',
'time.15min': '15 min',
'time.20min': '20 min',
'time.30min': '30 min',
'time.60min': '60 min',
'play.single_player': 'Single Player',
'play.single_player_desc': 'Play against computer',
'play.online_desc': 'Compete against real players',
'play.achievements': 'Achievements',
'play.gift': 'Gift',
'play.coming_soon': 'Coming Soon',
'play.login_required_online': 'Log in to play online',
'play.login_required_friend': 'Log in to challenge a friend',
'domino.title': 'Domino',
'domino.subtitle': 'First to reach the target wins!',
'domino.draw_boneyard': 'Draw from boneyard',
'domino.pass': 'Pass',
'domino.select_bot': 'Choose bot level',
'domino.bot_strategy': 'Each level has a different strategy',
'domino.beginner': 'Beginner',
'domino.beginner_desc': 'Plays randomly',
'domino.intermediate': 'Intermediate',
'domino.intermediate_desc': 'Prefers high points',
'domino.expert': 'Expert',
'domino.expert_desc': 'Strategic and tricky',
'domino.select_target': 'Choose target',
'domino.target_hint': 'First to reach the points wins the match',
'domino.50_points': '50 points',
'domino.50_desc': 'Quick match (~5 min)',
'domino.100_points': '100 points',
'domino.100_desc': 'Classic (~10 min)',
'domino.150_points': '150 points',
'domino.150_desc': 'Long match (~15 min)',
'ludo.title': 'Ludo',
'ludo.subtitle': 'First to get all pieces home wins!',
'ludo.local_play': 'Local Play',
'ludo.local_desc': 'Choose players and bots',
'ludo.online_desc': 'Play against real players',
'ludo.friend_desc': 'Invite your friends to play',
'ludo.settings_online': 'Online Settings',
'ludo.settings_play': 'Game Settings',
'ludo.settings_online_hint': 'Choose number of real players',
'ludo.settings_play_hint': 'Choose player layout',
'ludo.player_count': 'Player Count',
'ludo.classic': 'Classic',
'ludo.triple': 'Triple',
'ludo.duel': 'Duel',
'ludo.player_layout': 'Player Layout',
'ludo.bot_level': 'Bot Level',
'ludo.easy': 'Easy',
'ludo.medium': 'Medium',
'ludo.hard': 'Hard',
'ludo.seat_order': 'Seat Order',
'ludo.search_match': 'Find Match',
'ludo.start_play': 'Start Game',
'ludo.players_no_bot': '{n} players (no bot)',
'ludo.one_player_bots': '1 player + {n} bots',
'ludo.players_bots': '{h} players + {n} bots',
'ludo.red': 'Red',
'ludo.green': 'Green',
'ludo.yellow': 'Yellow',
'ludo.blue': 'Blue',
'ludo.searching': 'Searching...',
'ludo.searching_hint': 'Finding nearby players',
'ludo.place_first': '1st',
'ludo.place_second': '2nd',
'ludo.place_third': '3rd',
'ludo.place_fourth': '4th',
'backgammon.title': 'Backgammon',
'backgammon.subtitle': 'First to bear off all pieces wins!',
'backgammon.bot_desc': 'Choose level and variant',
'backgammon.online_desc': 'Play against real players',
'backgammon.friend_desc': 'Invite your friend to play',
'backgammon.settings_online': 'Online Settings',
'backgammon.settings_game': 'Game Settings',
'backgammon.game_type': 'Game Type',
'backgammon.sheshbesh': 'Shesh Besh',
'backgammon.mahbousa': 'Mahbousa',
'backgammon.thirtyone': '31',
'backgammon.match_length': 'Match Length',
'backgammon.short_match': 'Quick',
'backgammon.medium_match': 'Short',
'backgammon.normal_match': 'Normal',
'backgammon.long_match': 'Long',
'backgammon.doubling_cube': 'Doubling Cube',
'backgammon.enabled': 'Enabled',
'backgammon.disabled': 'Disabled',
'backgammon.searching': 'Searching...',
'backgammon.searching_hint': 'Finding a nearby player',
'backgammon.win_title': 'Congratulations! You won!',
'backgammon.lose_title': 'You lost this time...',
'backgammon.win_subtitle': 'Great performance!',
'backgammon.lose_subtitle': 'Try again!',
'backgammon.abandon_subtitle': 'Opponent left the game',
'backgammon.from_match': 'of {n}',
'backgammon.opponent_doubles': 'Opponent doubles!',
'backgammon.decline': 'Decline',
'backgammon.accept': 'Accept',
'backgammon.no_moves': 'No moves available!',
'backgammon.bot_accepted': 'Bot accepted! ×{n}',
'backgammon.bot_declined': 'Bot declined! You win the round',
'emote.good_move': 'Great move',
'emote.think': 'Thinking...',
'emote.hurry': 'Hurry up',
'emote.wow': 'Wow!',
'emote.laugh': 'Haha',
'emote.angry': 'Angry',
'emote.hello': 'Hello',
'emote.nice_game': 'Nice game!',
'emote.great_move': 'Great move!',
'emote.lucky': 'Lucky!',
'emote.think_faster': 'Think faster...',
'emote.rematch': 'Rematch?',
'analysis.title': 'Game Analysis',
'analysis.analyzing': 'Analyzing...',
'analysis.start': 'Start',
'analysis.engine_analyzing': 'Engine analyzing...',
'analysis.failed': 'Analysis failed',
'analysis.openings_book': 'Openings Book',
'review.title': 'Game Review',
'review.analyzing_moves': 'Analyzing each move...',
'review.move_classification': 'Move Classification',
'review.brilliant': '!! Brilliant',
'review.great': '! Great',
'review.best': 'Best move',
'review.good': '✓ Good',
'review.book': 'Book',
'review.inaccuracy': '?! Inaccuracy',
'review.mistake': '? Mistake',
'review.blunder': '?? Blunder',
'review.full_analysis': 'Full Analysis (move by move)',
'review.unknown_opening': 'Unknown',
'spectate.live': 'Live',
'spectate.black': 'Black',
'spectate.white': 'White',
'spectate.white_wins': 'White wins',
'spectate.black_wins': 'Black wins',
'spectate.game_over': 'Game over',
'history.no_matches': 'No matches yet — play to see them here',
'history.win': 'Win',
'history.loss': 'Loss',
'history.draw': 'Draw',
'history.insufficient_data': 'Insufficient data',
'history.matches_count': '{n} matches',
'result.copied': '✓ Copied',
'result.copy_pgn': 'Copy',
'result.share_pgn': 'Share PGN',
'result.analyze': 'Analyze Game',
'result.rematch': 'Rematch',
'classifier.excellent': 'Excellent',
'classifier.great': 'Great',
'classifier.good': 'Good',
'classifier.average': 'Average',
'classifier.poor': 'Poor',
'profile.edit_title': 'Edit Profile',
'profile.basic_info': 'Basic Info',
'profile.display_name_en': 'Display Name (English)',
'profile.display_name_ar': 'Display Name (Arabic)',
'profile.bio_en': 'Bio (English)',
'profile.bio_ar': 'Bio (Arabic)',
'profile.location': 'Location',
'profile.country': 'Country',
'profile.select_country': 'Select Country',
'profile.city': 'City',
'profile.preferred_language': 'Preferred Language',
'profile.fide_info': 'FIDE Info',
'profile.fide_classical': 'FIDE Classical Rating',
'profile.fide_rapid': 'FIDE Rapid Rating',
'profile.fide_blitz': 'FIDE Blitz Rating',
'profile.fide_title': 'FIDE Title',
'profile.save': 'Save Changes',
'profile.saving': 'Saving...',
'profile.save_success': 'Changes saved successfully',
'profile.save_failed': 'Failed to save changes',
'profile.uploading': 'Uploading...',
'profile.upload_failed': 'Failed to upload image',
'profile.matches': 'Matches',
'profile.wins': 'Wins',
'profile.streak': 'Streak',
'profile.rating_section': 'Rating',
'profile.chess_rapid': 'Chess (Rapid)',
'profile.chess_blitz': 'Chess (Blitz)',
'profile.chess_bullet': 'Chess (Bullet)',
'profile.org_section': 'Organization',
'profile.edit_btn': 'Edit Profile',
'profile.org_apply': 'Join Organization',
'profile.logout': 'Logout',
'profile.view_title': 'Profile',
'profile.add_friend': '➕ Add Friend',
'profile.friend_sent': '✓ Request sent',
'profile.pending_request': 'Friend request pending',
'profile.challenge': 'Challenge',
'profile.message': 'Message',
'profile.playing_now': 'Playing now — {game}',
'profile.watch': 'Watch',
'profile.member': 'Member',
'profile.pending_review': 'Under Review',
'profile.rejected': 'Rejected',
'profile.rejection_reason': 'Reason: {reason}',
'profile.membership_request': 'Membership Request',
'profile.no_org': 'Not a member of any organization yet',
'org.join_title': 'Join Organization',
'org.no_orgs': 'No organizations available',
'org.apply': 'Apply',
'org.reapply': 'Reapply',
'org.apply_title': 'Apply to join — {name}',
'org.doc_type': 'Document Type',
'org.notes': 'Notes (optional)',
'org.notes_placeholder': 'Message to organization admin...',
'org.proof_image': 'Proof Image',
'org.tap_to_select': 'Tap to select image',
'org.file_limit': 'PNG, JPG, WebP — max 5MB',
'org.file_too_large': 'File is larger than 5MB',
'org.submit': 'Submit Application',
'org.submitting': 'Submitting...',
'org.submit_success': 'Application submitted successfully',
'org.submit_failed': 'Failed to submit application',
'room.waiting_friend': 'Waiting for friend...',
'room.challenge_from': 'Challenge from {name}',
'room.send_invite': 'Send the invite to your friend',
'room.tap_accept': 'Tap accept to start',
'room.match_created': 'Match created, waiting for acceptance...',
'daily.title': 'Daily Reward',
'daily.streak_days': '{n} day streak',
'daily.day': 'Day {n}',
'daily.claimed_today': 'Today\'s reward claimed',
'daily.come_back': 'Come back tomorrow for a bigger reward!',
'daily.today_reward': 'Today\'s Reward',
'daily.claim_btn': 'Claim Reward',
'daily.hint': 'Each day you claim increases the amount • Day 7 = 300 coins!',
'daily.claiming': 'Claiming...',
'daily.failed': 'Failed — try again',
'challenges.title': 'Daily Challenges',
'challenges.all_done': 'All challenges completed today!',
'challenges.streak': '{n} days',
'challenges.claim': 'Claim',
'rank.bronze': 'Bronze',
'rank.silver': 'Silver',
'rank.gold': 'Gold',
'rank.platinum': 'Platinum',
'rank.diamond': 'Diamond',
'rank.master': 'Master',
'rank.grandmaster': 'Grandmaster',
'shop.insufficient_balance': 'Insufficient balance',
'shop.purchased': 'Purchased!',
'group.create': 'Create',
'group.no_groups': 'No groups yet',
'group.no_groups_hint': 'Create a group and invite your friends!',
'group.members': 'Members',
'group.owner': 'Owner',
'group.admin': 'Admin',
'group.play': 'Play',
'group.load_error': 'Error loading group',
'group.play_invite': 'Play invite',
'group.players': 'Players',
'group.join': 'Join',
'group.accepted': '✓ Accepted',
'group.invite_sent': 'Play invite sent!',
'group.create_title': 'Create Group',
'group.name_label': 'Group Name',
'group.select_friends': 'Choose friends to add',
'group.create_btn': 'Create Group',
'group.name_min': 'Enter group name (at least 2 characters)',
'group.creating': 'Creating...',
'group.created': 'Group created!',
'group.add': 'Add',
'group.leave': 'Leave Group',
'group.remove': 'Remove',
'group.remove_member': 'Remove Member',
'group.leave_confirm': 'Do you want to leave this group?',
'group.leave_btn': 'Leave',
'group.stay': 'Stay',
'auth.login_title': 'Login',
'auth.login_btn': 'Login',
'auth.register_link': 'Register now',
'auth.forgot_password': 'Forgot password?',
'auth.login_failed': 'Login failed',
'auth.invalid_email': 'Invalid email',
'auth.guest_login': 'Login as guest',
'auth.register_title': 'Create Account',
'auth.confirm_password': 'Confirm Password',
'auth.has_account': 'Already have an account?',
'auth.login_link': 'Login',
'auth.register_failed': 'Registration failed',
'auth.password_mismatch': 'Passwords do not match',
'auth.username_short': 'Username too short',
'auth.password_short': 'Password too short (min 6 characters)',
'app.tagline': 'Play with your friends',
'common.or': 'or',
'common.play_again': 'Play Again',
'tournament.title': 'Tournaments',
'tournament.upcoming': 'Upcoming',
'tournament.active': 'Active',
'tournament.completed': 'Completed',
'tournament.no_tournaments': 'No tournaments available',
'tournament.players': '{n} players',
'tournament.join': 'Join',
'tournament.joined': 'Joined',
'tournament.starts_in': 'Starts in {n}',
'tournament.entry_fee': 'Entry Fee',
'tournament.prize': 'Prize',
'tournament.free': 'Free',
'tournament.waiting': 'Waiting for players...',
'tournament.players_ready': '{n}/{total} players ready',
'tournament.starting_soon': 'Starting soon!',
'tournament.leave': 'Leave',
'tournament.ready': 'Ready',
'tournament.bracket': 'Tournament Bracket',
'tournament.round': 'Round {n}',
'tournament.final': 'Final',
'tournament.semifinal': 'Semifinal',
'tournament.quarterfinal': 'Quarterfinal',
'tournament.bye': 'Bye',
'tournament.match_starting': 'Match starting...',
'tournament.opponent': 'Opponent',
'tournament.round_of': 'Round {n}',
'tournament.champion': 'Champion!',
'tournament.eliminated': 'Eliminated',
'tournament.final_rank': 'Rank #{n}',
'tournament.prize_won': 'Prize: {n} coins',
'tournament.back_to_lobby': 'Back',
'tournament.hub_title': 'Live Tournaments',
'tournament.create': 'Create Tournament',
'tournament.browse': 'Browse Tournaments',
'tournament.arena': 'Arena',
'tournament.leave_arena': 'Leave Arena',
'tournament.no_results': 'No results yet',
'tournament.leaderboard': 'Leaderboard',
'tournament.tab_info': 'Info',
'tournament.tab_standings': 'Standings',
'tournament.tab_rounds': 'Rounds',
'tournament.tab_my_games': 'My Games',
'tournament.tab_bracket': 'Bracket',
'tournament.tab_arena': 'Arena',
'tournament.registration_open': 'Registration Open',
'tournament.coming_soon': 'Coming Soon',
'tournament.round_n': 'Round {n}/{total}',
'tournament.live_matches': 'Live Matches',
'tournament.no_match': 'No match found',
'spectate.watch': 'Watch',
'chess.rating': 'Rating',
'chess.rating_history': 'Rating History',
'chess.highest': 'Highest',
'chess.lowest': 'Lowest',
'chess.current': 'Current',
'puzzle.title': 'Chess Puzzle',
'puzzle.correct': 'Correct!',
'puzzle.wrong': 'Wrong answer',
'org.members': 'Members',
'org.game': 'Game',
'org.members_section': 'Members',
'org.join': 'Join Organization',
'org.joined': 'Joined',
'org.title': 'Organizations',
'org.join_btn': 'Join',
'org.doc_membership': 'Membership Card',
'org.doc_id': 'ID Card',
'org.doc_receipt': 'Payment Receipt',
'org.doc_other': 'Other',
'ludo.emote_sad': 'Sad',
'ludo.emote_strong': 'Strong',
'ludo.emote_cool': 'Cool',
'ludo.phrase_gl': 'Good luck!',
'ludo.phrase_hurry': 'Hurry up!',
'ludo.phrase_nice': 'Nice!',
'ludo.phrase_oops': 'Oops!',
'ludo.phrase_wow': 'Wow!'
} }
}; };
......
...@@ -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 {
......
...@@ -29,7 +29,7 @@ let COLORS = DEFAULT_COLORS; ...@@ -29,7 +29,7 @@ let COLORS = DEFAULT_COLORS;
let COLORS_LIGHT = DEFAULT_COLORS_LIGHT; let COLORS_LIGHT = DEFAULT_COLORS_LIGHT;
const PAWN_SLOTS = ['ludo_pawn_red', 'ludo_pawn_green', 'ludo_pawn_yellow', 'ludo_pawn_blue']; const PAWN_SLOTS = ['ludo_pawn_red', 'ludo_pawn_green', 'ludo_pawn_yellow', 'ludo_pawn_blue'];
const pawnImages = [null, null, null, null]; const pawnImages = [null, null, null, null];
let PLAYER_NAMES = ['أنت', 'Bot 1', 'Bot 2', 'Bot 3']; let PLAYER_NAMES = [t('common.you'), 'Bot 1', 'Bot 2', 'Bot 3'];
function lightenColor(hex, amount) { function lightenColor(hex, amount) {
const num = parseInt(hex.replace('#', ''), 16); const num = parseInt(hex.replace('#', ''), 16);
...@@ -80,18 +80,18 @@ export function mountGame(el, params) { ...@@ -80,18 +80,18 @@ export function mountGame(el, params) {
if (mode === 'live' && params.players) { if (mode === 'live' && params.players) {
PLAYER_NAMES = params.players.map((p, i) => { PLAYER_NAMES = params.players.map((p, i) => {
if (i === myPlayerIndex) return 'أنت'; if (i === myPlayerIndex) return t('common.you');
if (p.startsWith('bot')) return 'Bot ' + p.split('_')[1]; if (p.startsWith('bot')) return 'Bot ' + p.split('_')[1];
return 'لاعب ' + (i + 1); return t('common.player') + ' ' + (i + 1);
}); });
} else if (mode === 'local-multi') { } else if (mode === 'local-multi') {
PLAYER_NAMES = activeSeats.map((seatIdx, i) => { PLAYER_NAMES = activeSeats.map((seatIdx, i) => {
if (i === 0) return 'أنت'; if (i === 0) return t('common.you');
if (i < humanCount) return 'لاعب ' + (i + 1); if (i < humanCount) return t('common.player') + ' ' + (i + 1);
return 'Bot ' + (i - humanCount + 1); return 'Bot ' + (i - humanCount + 1);
}); });
} else { } else {
PLAYER_NAMES = ['أنت', 'Bot 1', 'Bot 2', 'Bot 3'].slice(0, numPlayers); PLAYER_NAMES = [t('common.you'), 'Bot 1', 'Bot 2', 'Bot 3'].slice(0, numPlayers);
} }
game = rules.createGame(numPlayers, activeSeats); game = rules.createGame(numPlayers, activeSeats);
...@@ -113,7 +113,7 @@ export function mountGame(el, params) { ...@@ -113,7 +113,7 @@ export function mountGame(el, params) {
const isMe = i === myPlayerIndex; const isMe = i === myPlayerIndex;
const isBot = PLAYER_NAMES[i]?.startsWith('Bot'); const isBot = PLAYER_NAMES[i]?.startsWith('Bot');
const avatar = isMe && player.avatar_url ? `<img src="${player.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` : isBot ? '🤖' : '👤'; const avatar = isMe && player.avatar_url ? `<img src="${player.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` : isBot ? '🤖' : '👤';
const name = isMe ? (player.display_name || player.username || 'أنت') : (PLAYER_NAMES[i] || 'Bot'); const name = isMe ? (player.display_name || player.username || t('common.you')) : (PLAYER_NAMES[i] || 'Bot');
const boardSlot = activeSeats[i] ?? i; const boardSlot = activeSeats[i] ?? i;
const level = isMe ? `Lv.${player.level || 1}` : (isBot ? '' : ''); const level = isMe ? `Lv.${player.level || 1}` : (isBot ? '' : '');
return { i, avatar, name, level, color: COLORS[boardSlot] }; return { i, avatar, name, level, color: COLORS[boardSlot] };
...@@ -137,7 +137,7 @@ export function mountGame(el, params) { ...@@ -137,7 +137,7 @@ export function mountGame(el, params) {
<button class="btn btn-secondary" id="emote-btn" style="min-height:44px;min-width:44px;padding:0;font-size:18px;border-radius:50%;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);">😄</button> <button class="btn btn-secondary" id="emote-btn" style="min-height:44px;min-width:44px;padding:0;font-size:18px;border-radius:50%;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);">😄</button>
<div id="dice-box" style="width:56px;height:56px;background:linear-gradient(145deg,#ffffff,#f0ede8);border-radius:12px;display:grid;grid-template:repeat(3,1fr)/repeat(3,1fr);padding:7px;box-shadow:0 4px 12px rgba(0,0,0,0.4),inset 0 2px 0 rgba(255,255,255,0.9),0 0 0 2px rgba(228,172,56,0.15);transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1),opacity 0.3s ease,filter 0.3s ease;"> <div id="dice-box" style="width:56px;height:56px;background:linear-gradient(145deg,#ffffff,#f0ede8);border-radius:12px;display:grid;grid-template:repeat(3,1fr)/repeat(3,1fr);padding:7px;box-shadow:0 4px 12px rgba(0,0,0,0.4),inset 0 2px 0 rgba(255,255,255,0.9),0 0 0 2px rgba(228,172,56,0.15);transition:transform 0.15s cubic-bezier(0.34,1.56,0.64,1),opacity 0.3s ease,filter 0.3s ease;">
</div> </div>
<button class="btn btn-primary" id="roll-btn" style="font-size:15px;padding:14px 32px;min-height:52px;border-radius:14px;background:linear-gradient(135deg,#E4AC38,#F59E0B);box-shadow:0 4px 14px rgba(228,172,56,0.3);font-weight:800;transition:background 0.3s ease,color 0.3s ease,opacity 0.3s ease;" disabled>ارمِ النرد</button> <button class="btn btn-primary" id="roll-btn" style="font-size:15px;padding:14px 32px;min-height:52px;border-radius:14px;background:linear-gradient(135deg,#E4AC38,#F59E0B);box-shadow:0 4px 14px rgba(228,172,56,0.3);font-weight:800;transition:background 0.3s ease,color 0.3s ease,opacity 0.3s ease;" disabled>${t('game.roll_dice')}</button>
<div id="turn-status" style="position:absolute;top:0;left:50%;transform:translateX(-50%);font-size:11px;font-weight:700;padding:2px 12px;border-radius:0 0 8px 8px;display:none;"></div> <div id="turn-status" style="position:absolute;top:0;left:50%;transform:translateX(-50%);font-size:11px;font-weight:700;padding:2px 12px;border-radius:0 0 8px 8px;display:none;"></div>
</div> </div>
</div> </div>
...@@ -240,7 +240,7 @@ export function mountGame(el, params) { ...@@ -240,7 +240,7 @@ export function mountGame(el, params) {
const panel = el.querySelector(`#pp-${i}`); const panel = el.querySelector(`#pp-${i}`);
if (panel) { if (panel) {
const nameEl = panel.querySelector('.pp-name'); const nameEl = panel.querySelector('.pp-name');
const displayName = profile.display_name || profile.username || 'لاعب'; const displayName = profile.display_name || profile.username || t('common.player');
if (nameEl) nameEl.textContent = displayName; if (nameEl) nameEl.textContent = displayName;
PLAYER_NAMES[i] = displayName; PLAYER_NAMES[i] = displayName;
const avatarEl = panel.querySelector('.pp-avatar'); const avatarEl = panel.querySelector('.pp-avatar');
...@@ -385,7 +385,7 @@ async function botLoop(el) { ...@@ -385,7 +385,7 @@ async function botLoop(el) {
const botPanel = el.querySelector(`#pp-${game.currentPlayer}`); const botPanel = el.querySelector(`#pp-${game.currentPlayer}`);
const botStatusEl = botPanel ? botPanel.querySelector('.pp-status') : null; const botStatusEl = botPanel ? botPanel.querySelector('.pp-status') : null;
const originalStatusText = botStatusEl ? botStatusEl.textContent : ''; const originalStatusText = botStatusEl ? botStatusEl.textContent : '';
if (botStatusEl) botStatusEl.textContent = 'يفكر...'; if (botStatusEl) botStatusEl.textContent = t('game.thinking');
await new Promise(r => setTimeout(r, thinkDelay)); await new Promise(r => setTimeout(r, thinkDelay));
if (game.gameOver || isMyTurn()) { if (botStatusEl) botStatusEl.textContent = originalStatusText; return; } if (game.gameOver || isMyTurn()) { if (botStatusEl) botStatusEl.textContent = originalStatusText; return; }
...@@ -398,7 +398,7 @@ async function botLoop(el) { ...@@ -398,7 +398,7 @@ async function botLoop(el) {
const decideDelay = (personality.thinkMin * 0.5 + Math.random() * (personality.thinkMax - personality.thinkMin) * 0.5) * turboMul; const decideDelay = (personality.thinkMin * 0.5 + Math.random() * (personality.thinkMax - personality.thinkMin) * 0.5) * turboMul;
// Show thinking indicator again while deciding // Show thinking indicator again while deciding
if (botStatusEl) botStatusEl.textContent = 'يفكر...'; if (botStatusEl) botStatusEl.textContent = t('game.thinking');
await new Promise(r => setTimeout(r, decideDelay)); await new Promise(r => setTimeout(r, decideDelay));
if (botStatusEl) botStatusEl.textContent = originalStatusText; if (botStatusEl) botStatusEl.textContent = originalStatusText;
if (game.gameOver || isMyTurn()) return; if (game.gameOver || isMyTurn()) return;
...@@ -665,8 +665,8 @@ function showOpponentPopup(el, profile) { ...@@ -665,8 +665,8 @@ function showOpponentPopup(el, profile) {
<div style="font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:4px;">${profile.display_name || profile.username}</div> <div style="font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:4px;">${profile.display_name || profile.username}</div>
<div style="font-size:11px;color:#64748b;margin-bottom:14px;">Level ${profile.level || 1}</div> <div style="font-size:11px;color:#64748b;margin-bottom:14px;">Level ${profile.level || 1}</div>
<div style="display:flex;gap:8px;justify-content:center;"> <div style="display:flex;gap:8px;justify-content:center;">
<button id="opp-add-friend" style="padding:8px 16px;background:#2563EB;border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;">➕ صديق</button> <button id="opp-add-friend" style="padding:8px 16px;background:#2563EB;border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;">➕ ${t('mp.add_friend')}</button>
<button id="opp-close" style="padding:8px 16px;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="opp-close" style="padding:8px 16px;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);
...@@ -674,7 +674,7 @@ function showOpponentPopup(el, profile) { ...@@ -674,7 +674,7 @@ function showOpponentPopup(el, profile) {
popup.querySelector('#opp-close').addEventListener('click', () => popup.remove()); popup.querySelector('#opp-close').addEventListener('click', () => popup.remove());
popup.querySelector('#opp-add-friend').addEventListener('click', async () => { popup.querySelector('#opp-add-friend').addEventListener('click', async () => {
await mp.addFriendFromGame(profile.id); await mp.addFriendFromGame(profile.id);
popup.querySelector('#opp-add-friend').textContent = '✓ تم'; popup.querySelector('#opp-add-friend').textContent = t('common.done');
popup.querySelector('#opp-add-friend').style.background = '#34D399'; popup.querySelector('#opp-add-friend').style.background = '#34D399';
juice.hapticLight(); juice.hapticLight();
setTimeout(() => popup.remove(), 1000); setTimeout(() => popup.remove(), 1000);
...@@ -1137,7 +1137,7 @@ function checkTurboMode(el) { ...@@ -1137,7 +1137,7 @@ function checkTurboMode(el) {
game.turboMode = true; game.turboMode = true;
const banner = document.createElement('div'); const banner = document.createElement('div');
banner.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(228,172,56,0.95);color:#000;padding:10px 24px;border-radius:12px;font-size:14px;font-weight:700;z-index:100;'; banner.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(228,172,56,0.95);color:#000;padding:10px 24px;border-radius:12px;font-size:14px;font-weight:700;z-index:100;';
banner.textContent = '⚡ وضع السرعة!'; banner.textContent = t('game.speed_mode');
el.querySelector('#ludo-wrap')?.appendChild(banner); el.querySelector('#ludo-wrap')?.appendChild(banner);
banner.animate([{opacity:0,transform:'translate(-50%,-50%) scale(0.5)'},{opacity:1,transform:'translate(-50%,-50%) scale(1)'},{opacity:0,transform:'translate(-50%,-50%) translateY(-20px)'}], {duration:2000}).onfinish = () => banner.remove(); banner.animate([{opacity:0,transform:'translate(-50%,-50%) scale(0.5)'},{opacity:1,transform:'translate(-50%,-50%) scale(1)'},{opacity:0,transform:'translate(-50%,-50%) translateY(-20px)'}], {duration:2000}).onfinish = () => banner.remove();
juice.hapticHeavy(); juice.hapticHeavy();
...@@ -1190,13 +1190,13 @@ function updatePanels(el) { ...@@ -1190,13 +1190,13 @@ function updatePanels(el) {
? COLORS[slot] ? COLORS[slot]
: 'linear-gradient(135deg,#E4AC38,#F59E0B)'; : 'linear-gradient(135deg,#E4AC38,#F59E0B)';
turnStatus.style.color = game.mode === 'local-multi' && slot === 2 ? '#000' : '#000'; turnStatus.style.color = game.mode === 'local-multi' && slot === 2 ? '#000' : '#000';
turnStatus.textContent = game.mode === 'local-multi' ? `دور ${currentName}` : 'دورك!'; turnStatus.textContent = game.mode === 'local-multi' ? t('game.turn_of', { name: currentName }) : t('game.your_turn');
turnStatus.style.animation = 'fadeIn 0.3s ease-out'; turnStatus.style.animation = 'fadeIn 0.3s ease-out';
} else { } else {
turnStatus.style.display = 'block'; turnStatus.style.display = 'block';
turnStatus.style.background = 'rgba(255,255,255,0.08)'; turnStatus.style.background = 'rgba(255,255,255,0.08)';
turnStatus.style.color = '#94a3b8'; turnStatus.style.color = '#94a3b8';
turnStatus.textContent = isBot ? `${currentName} يفكر...` : `دور ${currentName}`; turnStatus.textContent = isBot ? t('game.bot_thinking', { name: currentName }) : t('game.turn_of', { name: currentName });
turnStatus.style.animation = 'fadeIn 0.3s ease-out'; turnStatus.style.animation = 'fadeIn 0.3s ease-out';
} }
} }
...@@ -1219,7 +1219,7 @@ function updatePanels(el) { ...@@ -1219,7 +1219,7 @@ function updatePanels(el) {
if (myTurn) { if (myTurn) {
btn.style.visibility = 'visible'; btn.style.visibility = 'visible';
btn.disabled = !canRoll; btn.disabled = !canRoll;
btn.textContent = game.rolled ? 'اختر قطعة' : 'ارمِ النرد'; btn.textContent = game.rolled ? t('game.choose_piece') : t('game.roll_dice');
btn.style.opacity = canRoll ? '1' : '0.5'; btn.style.opacity = canRoll ? '1' : '0.5';
btn.style.background = 'linear-gradient(135deg,#E4AC38,#F59E0B)'; btn.style.background = 'linear-gradient(135deg,#E4AC38,#F59E0B)';
btn.style.color = '#000'; btn.style.color = '#000';
...@@ -1229,7 +1229,7 @@ function updatePanels(el) { ...@@ -1229,7 +1229,7 @@ function updatePanels(el) {
} else { } else {
btn.style.visibility = 'visible'; btn.style.visibility = 'visible';
btn.disabled = true; btn.disabled = true;
btn.textContent = isBot ? 'Bot يلعب...' : 'انتظر دورك'; btn.textContent = isBot ? t('game.bot_thinking', { name: 'Bot' }) : t('game.opponent_turn');
btn.style.opacity = '0.5'; btn.style.opacity = '0.5';
btn.style.background = 'rgba(255,255,255,0.06)'; btn.style.background = 'rgba(255,255,255,0.06)';
btn.style.color = '#64748b'; btn.style.color = '#64748b';
...@@ -1368,11 +1368,11 @@ function renderMiniDice(miniDice, value) { ...@@ -1368,11 +1368,11 @@ function renderMiniDice(miniDice, value) {
async function handleExit(el) { async function handleExit(el) {
if (game.gameOver) return; if (game.gameOver) return;
const confirmed = await modal.confirm('هل تريد الخروج من المباراة؟', { const confirmed = await modal.confirm(t('game.leave_confirm'), {
title: 'مغادرة', title: t('game.leave_title'),
icon: '🚪', icon: '🚪',
confirmText: 'نعم، اخرج', confirmText: t('game.leave'),
cancelText: 'ابقَ', cancelText: t('game.stay'),
danger: true danger: true
}); });
if (!confirmed) return; if (!confirmed) return;
...@@ -1467,23 +1467,23 @@ function endGame(el) { ...@@ -1467,23 +1467,23 @@ function endGame(el) {
// ===== SOCIAL: EMOTES + PHRASES ===== // ===== SOCIAL: EMOTES + PHRASES =====
const EMOTES = [ const EMOTES = [
{ key: '😂', label: 'ههه' }, { key: '😂', get label() { return t('emote.laugh'); } },
{ key: '😮', label: 'واو' }, { key: '😮', get label() { return t('emote.wow'); } },
{ key: '😡', label: 'غضب' }, { key: '😡', get label() { return t('emote.angry'); } },
{ key: '👏', label: 'برافو' }, { key: '👏', get label() { return t('emote.good_move'); } },
{ key: '🔥', label: 'حماس' }, { key: '🔥', get label() { return t('emote.hurry'); } },
{ key: '😢', label: 'حزين' }, { key: '😢', get label() { return t('ludo.emote_sad'); } },
{ key: '💪', label: 'قوي' }, { key: '💪', get label() { return t('ludo.emote_strong'); } },
{ key: '😎', label: 'كول' }, { key: '😎', get label() { return t('ludo.emote_cool'); } },
]; ];
const PHRASES = [ const PHRASES = [
{ key: 'gl', text: 'حظ سعيد!' }, { key: 'gl', get text() { return t('ludo.phrase_gl'); } },
{ key: 'gg', text: 'GG!' }, { key: 'gg', text: 'GG!' },
{ key: 'hurry', text: 'يلا بسرعة!' }, { key: 'hurry', get text() { return t('ludo.phrase_hurry'); } },
{ key: 'nice', text: 'نايس!' }, { key: 'nice', get text() { return t('ludo.phrase_nice'); } },
{ key: 'oops', text: 'يا ساتر!' }, { key: 'oops', get text() { return t('ludo.phrase_oops'); } },
{ key: 'wow', text: 'ما شاء الله!' }, { key: 'wow', get text() { return t('ludo.phrase_wow'); } },
]; ];
let emoteCooldown = false; let emoteCooldown = false;
......
...@@ -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');
......
...@@ -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';
export function mountChallenge(el) { export function mountChallenge(el) {
...@@ -10,10 +11,10 @@ export function mountChallenge(el) { ...@@ -10,10 +11,10 @@ export function mountChallenge(el) {
<div class="cf-layout"> <div class="cf-layout">
<div class="cf-header"> <div class="cf-header">
<button id="cf-back" class="cf-back-btn">→</button> <button id="cf-back" class="cf-back-btn">→</button>
<div class="cf-title">${emoji('challenge_swords', '⚔️', 18)} تحدّي صديق</div> <div class="cf-title">${emoji('challenge_swords', '⚔️', 18)} ${t('challenge.title')}</div>
</div> </div>
<div class="cf-body" id="cf-body"> <div class="cf-body" id="cf-body">
<div style="text-align:center;padding:32px;color:#64748b;font-size:13px;">${emoji('loading', '⏳', 14)} جاري تحميل الأصدقاء...</div> <div style="text-align:center;padding:32px;color:#64748b;font-size:13px;">${emoji('loading', '⏳', 14)} ${t('challenge.loading_friends')}</div>
</div> </div>
</div> </div>
<style> <style>
...@@ -55,9 +56,9 @@ async function loadFriendsForChallenge(el) { ...@@ -55,9 +56,9 @@ async function loadFriendsForChallenge(el) {
body.innerHTML = ` body.innerHTML = `
<div style="text-align:center;padding:48px 24px;"> <div style="text-align:center;padding:48px 24px;">
<div style="font-size:48px;margin-bottom:12px;opacity:0.5;">${emoji('people', '👥', 48)}</div> <div style="font-size:48px;margin-bottom:12px;opacity:0.5;">${emoji('people', '👥', 48)}</div>
<div style="font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:6px;">لا يوجد أصدقاء بعد</div> <div style="font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:6px;">${t('challenge.no_friends')}</div>
<div style="font-size:12px;color:#64748b;margin-bottom:16px;">أضف أصدقاء أولاً لتتمكن من تحديهم</div> <div style="font-size:12px;color:#64748b;margin-bottom:16px;">${t('challenge.no_friends_hint')}</div>
<button class="btn btn-primary" id="cf-go-social" style="font-size:13px;padding:10px 24px;">${emoji('search_icon', '🔍', 13)} ابحث عن لاعبين</button> <button class="btn btn-primary" id="cf-go-social" style="font-size:13px;padding:10px 24px;">${emoji('search_icon', '🔍', 13)} ${t('social.search_players')}</button>
</div>`; </div>`;
body.querySelector('#cf-go-social')?.addEventListener('click', () => scene.push('friends')); body.querySelector('#cf-go-social')?.addEventListener('click', () => scene.push('friends'));
return; return;
...@@ -69,12 +70,12 @@ async function loadFriendsForChallenge(el) { ...@@ -69,12 +70,12 @@ async function loadFriendsForChallenge(el) {
let html = ''; let html = '';
if (online.length > 0) { if (online.length > 0) {
html += `<div class="cf-section-title">${emoji('green_circle', '🟢', 11)} متصلين الآن (${online.length})</div>`; html += `<div class="cf-section-title">${emoji('green_circle', '🟢', 11)} ${t('challenge.online_now', { n: online.length })}</div>`;
html += online.map(f => renderChallengeCard(f, true)).join(''); html += online.map(f => renderChallengeCard(f, true)).join('');
} }
if (offline.length > 0) { if (offline.length > 0) {
html += `<div class="cf-section-title" style="margin-top:${online.length > 0 ? '16px' : '0'};">${emoji('gray_circle', '⚪', 11)} غير متصلين</div>`; html += `<div class="cf-section-title" style="margin-top:${online.length > 0 ? '16px' : '0'};">${emoji('gray_circle', '⚪', 11)} ${t('challenge.offline')}</div>`;
html += offline.map(f => renderChallengeCard(f, false)).join(''); html += offline.map(f => renderChallengeCard(f, false)).join('');
} }
...@@ -88,7 +89,7 @@ async function loadFriendsForChallenge(el) { ...@@ -88,7 +89,7 @@ async function loadFriendsForChallenge(el) {
juice.hapticLight(); juice.hapticLight();
const uid = btn.dataset.challenge; const uid = btn.dataset.challenge;
const card = btn.closest('.cf-card'); const card = btn.closest('.cf-card');
const name = card?.querySelector('.cf-card-name')?.textContent || 'صديق'; const name = card?.querySelector('.cf-card-name')?.textContent || t('common.friend');
const friend = friends.find(f => f.id === uid); const friend = friends.find(f => f.id === uid);
showChallengeOptions(el, uid, name, friend); showChallengeOptions(el, uid, name, friend);
}); });
...@@ -107,7 +108,7 @@ async function loadFriendsForChallenge(el) { ...@@ -107,7 +108,7 @@ async function loadFriendsForChallenge(el) {
}); });
} catch (e) { } catch (e) {
body.innerHTML = `<div style="text-align:center;color:#ef4444;padding:24px;">فشل التحميل — <button id="cf-retry" style="color:#3b82f6;background:none;border:none;text-decoration:underline;cursor:pointer;font-family:inherit;">حاول مرة أخرى</button></div>`; body.innerHTML = `<div style="text-align:center;color:#ef4444;padding:24px;">${t('common.error_load')} — <button id="cf-retry" style="color:#3b82f6;background:none;border:none;text-decoration:underline;cursor:pointer;font-family:inherit;">${t('social.retry_load')}</button></div>`;
body.querySelector('#cf-retry')?.addEventListener('click', () => loadFriendsForChallenge(el)); body.querySelector('#cf-retry')?.addEventListener('click', () => loadFriendsForChallenge(el));
} }
} }
...@@ -120,10 +121,10 @@ function renderChallengeCard(f, isOnline) { ...@@ -120,10 +121,10 @@ function renderChallengeCard(f, isOnline) {
${isOnline ? '<div class="cf-card-online"></div>' : ''} ${isOnline ? '<div class="cf-card-online"></div>' : ''}
</div> </div>
<div class="cf-card-info"> <div class="cf-card-info">
<div class="cf-card-name">${f.display_name || f.username || 'لاعب'}</div> <div class="cf-card-name">${f.display_name || f.username || t('common.player')}</div>
<div class="cf-card-sub">${isOnline ? 'متصل الآن' : 'غير متصل'}${f.level ? ` • مستوى ${f.level}` : ''}</div> <div class="cf-card-sub">${isOnline ? t('common.online') : t('common.offline')}${f.level ? ` • ${t('common.level', { n: f.level })}` : ''}</div>
</div> </div>
<button class="cf-card-action" data-challenge="${f.id}">${emoji('challenge_swords', '⚔️', 12)} تحدّي</button> <button class="cf-card-action" data-challenge="${f.id}">${emoji('challenge_swords', '⚔️', 12)} ${t('challenge.title')}</button>
</div> </div>
`; `;
} }
...@@ -139,27 +140,27 @@ function showChallengeOptions(el, targetId, targetName, friendProfile) { ...@@ -139,27 +140,27 @@ function showChallengeOptions(el, targetId, targetName, friendProfile) {
<div style="background:#1a1a2e;border-radius:20px 20px 0 0;padding:24px 20px;width:100%;max-width:400px;animation:slideUp 0.25s ease;"> <div style="background:#1a1a2e;border-radius:20px 20px 0 0;padding:24px 20px;width:100%;max-width:400px;animation:slideUp 0.25s ease;">
<div style="width:40px;height:4px;background:rgba(255,255,255,0.15);border-radius:2px;margin:0 auto 16px;"></div> <div style="width:40px;height:4px;background:rgba(255,255,255,0.15);border-radius:2px;margin:0 auto 16px;"></div>
<div style="text-align:center;margin-bottom:16px;"> <div style="text-align:center;margin-bottom:16px;">
<div style="font-size:15px;font-weight:700;color:#f8fafc;">تحدّي ${targetName}</div> <div style="font-size:15px;font-weight:700;color:#f8fafc;">${t('challenge.title')} ${targetName}</div>
<div style="font-size:12px;color:#64748b;margin-top:4px;">اختر اللعبة ونوع الوقت</div> <div style="font-size:12px;color:#64748b;margin-top:4px;">${t('challenge.select_game')}</div>
</div> </div>
<!-- Game selection --> <!-- Game selection -->
<div style="display:flex;gap:8px;margin-bottom:14px;"> <div style="display:flex;gap:8px;margin-bottom:14px;">
<button class="cfo-game active" data-game="chess" style="flex:1;padding:12px;border-radius:12px;background:#2563EB;border:2px solid #2563EB;color:#fff;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;transition:all 0.15s;">♟ شطرنج</button> <button class="cfo-game active" data-game="chess" style="flex:1;padding:12px;border-radius:12px;background:#2563EB;border:2px solid #2563EB;color:#fff;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;transition:all 0.15s;">♟ ${t('game.chess')}</button>
<button class="cfo-game" data-game="ludo" style="flex:1;padding:12px;border-radius:12px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;transition:all 0.15s;">🎲 لودو</button> <button class="cfo-game" data-game="ludo" style="flex:1;padding:12px;border-radius:12px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;transition:all 0.15s;">🎲 ${t('game.ludo')}</button>
<button class="cfo-game" data-game="domino" style="flex:1;padding:12px;border-radius:12px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;transition:all 0.15s;">🁣 دومينو</button> <button class="cfo-game" data-game="domino" style="flex:1;padding:12px;border-radius:12px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;transition:all 0.15s;">🁣 ${t('game.domino')}</button>
</div> </div>
<!-- Time control --> <!-- Time control -->
<div id="cfo-time" style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:20px;"> <div id="cfo-time" style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:20px;">
<button class="cfo-tc" data-tc="bullet_1_0">⚡ 1 د</button> <button class="cfo-tc" data-tc="bullet_1_0">⚡ ${t('time.1min')}</button>
<button class="cfo-tc active" data-tc="blitz_3_0">🔥 3 د</button> <button class="cfo-tc active" data-tc="blitz_3_0">🔥 ${t('time.3min')}</button>
<button class="cfo-tc" data-tc="blitz_5_0">💨 5 د</button> <button class="cfo-tc" data-tc="blitz_5_0">💨 ${t('time.5min')}</button>
<button class="cfo-tc" data-tc="rapid_10_0">🕐 10 د</button> <button class="cfo-tc" data-tc="rapid_10_0">🕐 ${t('time.10min')}</button>
</div> </div>
<button class="btn btn-primary" id="cfo-send" style="width:100%;min-height:50px;font-size:15px;font-weight:700;border-radius:14px;">⚔️ أرسل التحدي</button> <button class="btn btn-primary" id="cfo-send" style="width:100%;min-height:50px;font-size:15px;font-weight:700;border-radius:14px;">⚔️ ${t('challenge.send')}</button>
<button id="cfo-cancel" style="width:100%;margin-top:10px;background:none;border:none;color:#64748b;font-size:13px;cursor:pointer;font-family:inherit;padding:10px;">إلغاء</button> <button id="cfo-cancel" style="width:100%;margin-top:10px;background:none;border:none;color:#64748b;font-size:13px;cursor:pointer;font-family:inherit;padding:10px;">${t('common.cancel')}</button>
</div> </div>
<style> <style>
@keyframes slideUp { from{transform:translateY(100%)}to{transform:none} } @keyframes slideUp { from{transform:translateY(100%)}to{transform:none} }
...@@ -194,7 +195,7 @@ function showChallengeOptions(el, targetId, targetName, friendProfile) { ...@@ -194,7 +195,7 @@ function showChallengeOptions(el, targetId, targetName, friendProfile) {
dialog.querySelector('#cfo-send').addEventListener('click', async () => { dialog.querySelector('#cfo-send').addEventListener('click', async () => {
const sendBtn = dialog.querySelector('#cfo-send'); const sendBtn = dialog.querySelector('#cfo-send');
sendBtn.disabled = true; sendBtn.disabled = true;
sendBtn.textContent = '⏳ جاري الإرسال...'; sendBtn.textContent = t('challenge.sending');
try { try {
const res = await net.post('friends.php', { const res = await net.post('friends.php', {
...@@ -205,13 +206,13 @@ function showChallengeOptions(el, targetId, targetName, friendProfile) { ...@@ -205,13 +206,13 @@ function showChallengeOptions(el, targetId, targetName, friendProfile) {
}); });
if (res.error) { sendBtn.textContent = res.error; sendBtn.disabled = false; return; } if (res.error) { sendBtn.textContent = res.error; sendBtn.disabled = false; return; }
if (!res.match_id) { sendBtn.textContent = 'فشل إنشاء المباراة'; sendBtn.disabled = false; return; } if (!res.match_id) { sendBtn.textContent = t('challenge.failed_create'); sendBtn.disabled = false; return; }
// Also send a chat message // Also send a chat message
net.post('chat.php', { net.post('chat.php', {
action: 'send', action: 'send',
friend_id: targetId, friend_id: targetId,
content: `أرسل تحدي ${selectedGame === 'ludo' ? 'لودو' : selectedGame === 'domino' ? 'دومينو' : 'شطرنج'}`, content: t('challenge.sent_msg', { game: selectedGame === 'ludo' ? t('game.ludo') : selectedGame === 'domino' ? t('game.domino') : t('game.chess') }),
message_type: 'invite', message_type: 'invite',
metadata: { game_key: selectedGame, time_control: selectedTc, match_id: res.match_id } metadata: { game_key: selectedGame, time_control: selectedTc, match_id: res.match_id }
}).catch(() => {}); }).catch(() => {});
...@@ -231,7 +232,7 @@ function showChallengeOptions(el, targetId, targetName, friendProfile) { ...@@ -231,7 +232,7 @@ function showChallengeOptions(el, targetId, targetName, friendProfile) {
isHost: true isHost: true
}); });
} catch (e) { } catch (e) {
sendBtn.textContent = 'فشل — حاول مرة أخرى'; sendBtn.textContent = t('challenge.failed_retry');
sendBtn.disabled = false; sendBtn.disabled = false;
} }
}); });
......
...@@ -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;
} }
......
...@@ -17,10 +17,10 @@ function getGames() { ...@@ -17,10 +17,10 @@ function getGames() {
const bgSec = getColor('backgammon_secondary', '#EF4444'); const bgSec = getColor('backgammon_secondary', '#EF4444');
return [ return [
{ key: 'chess', name: 'شطرنج', nameEn: 'Chess', color: chessPri, secondary: chessSec, icon: '♟', gradient: `linear-gradient(135deg, ${chessPri}, ${chessSec})` }, { key: 'chess', name: t('game.chess'), nameEn: 'Chess', color: chessPri, secondary: chessSec, icon: '♟', gradient: `linear-gradient(135deg, ${chessPri}, ${chessSec})` },
{ key: 'domino', name: 'دومينو', nameEn: 'Domino', color: dominoPri, secondary: dominoSec, icon: '🁣', gradient: `linear-gradient(135deg, ${dominoPri}, ${dominoSec})` }, { key: 'domino', name: t('game.domino'), nameEn: 'Domino', color: dominoPri, secondary: dominoSec, icon: '🁣', gradient: `linear-gradient(135deg, ${dominoPri}, ${dominoSec})` },
{ key: 'ludo', name: 'لودو', nameEn: 'Ludo', color: ludoPri, secondary: ludoSec, icon: '🎲', gradient: `linear-gradient(135deg, ${ludoPri}, ${ludoSec})` }, { key: 'ludo', name: t('game.ludo'), nameEn: 'Ludo', color: ludoPri, secondary: ludoSec, icon: '🎲', gradient: `linear-gradient(135deg, ${ludoPri}, ${ludoSec})` },
{ key: 'backgammon', name: 'طاولة', nameEn: 'Backgammon', color: bgPri, secondary: bgSec, icon: '◎', gradient: `linear-gradient(135deg, ${bgPri}, ${bgSec})` } { key: 'backgammon', name: t('game.backgammon'), nameEn: 'Backgammon', color: bgPri, secondary: bgSec, icon: '◎', gradient: `linear-gradient(135deg, ${bgPri}, ${bgSec})` }
]; ];
} }
...@@ -32,24 +32,24 @@ export function mountTable(el) { ...@@ -32,24 +32,24 @@ export function mountTable(el) {
el.innerHTML = ` el.innerHTML = `
<div class="play-home"> <div class="play-home">
<!-- Player greeting --> <!-- Player greeting -->
<div style="width:100%;max-width:340px;margin-bottom:12px;display:flex;align-items:center;justify-content:space-between;"> <div style="width:100%;max-width:var(--home-max-width);margin-bottom:var(--home-greeting-margin);display:flex;align-items:center;justify-content:space-between;">
<div style="font-size:16px;font-weight:700;color:#f8fafc;">أهلاً ${username} 👋</div> <div style="font-size:var(--home-greeting-font);font-weight:700;color:var(--text-primary);">${username} 👋</div>
<div style="font-size:11px;color:#64748b;">المستوى ${player?.level || 1}</div> <div style="font-size:var(--gm-btn-sub-font);color:var(--text-muted);">Lv. ${player?.level || 1}</div>
</div> </div>
<!-- Quick actions row --> <!-- Quick actions row -->
<div id="daily-widget" style="display:flex;gap:8px;width:100%;max-width:340px;margin-bottom:16px;"> <div id="daily-widget" style="display:flex;gap:var(--quick-row-gap);width:100%;max-width:var(--home-max-width);margin-bottom:var(--quick-row-margin);">
<button class="quick-btn" id="btn-challenge-friend"> <button class="quick-btn" id="btn-challenge-friend">
<span class="qb-icon" style="background:linear-gradient(135deg,#7c3aed,#a855f7);">${emoji('challenge_swords', '⚔️', 20)}</span> <span class="qb-icon" style="background:linear-gradient(135deg,#7c3aed,#a855f7);">${emoji('challenge_swords', '⚔️', 20)}</span>
<span class="qb-label">تحدّي صديق</span> <span class="qb-label">${t('challenge.title')}</span>
</button> </button>
<button class="quick-btn" id="btn-achievements"> <button class="quick-btn" id="btn-achievements">
<span class="qb-icon" style="background:linear-gradient(135deg,#854d0e,#ca8a04);">${emoji('trophy', '🏆', 20)}</span> <span class="qb-icon" style="background:linear-gradient(135deg,#854d0e,#ca8a04);">${emoji('trophy', '🏆', 20)}</span>
<span class="qb-label">إنجازات</span> <span class="qb-label">${t('play.achievements')}</span>
</button> </button>
<button class="quick-btn breathe-glow" id="btn-daily-reward"> <button class="quick-btn breathe-glow" id="btn-daily-reward">
<span class="qb-icon" style="background:linear-gradient(135deg,#92400e,#e4ac38);">${emoji('gift', '🎁', 20)}</span> <span class="qb-icon" style="background:linear-gradient(135deg,#92400e,#e4ac38);">${emoji('gift', '🎁', 20)}</span>
<span class="qb-label">هدية</span> <span class="qb-label">${t('play.gift')}</span>
</button> </button>
</div> </div>
...@@ -63,7 +63,7 @@ export function mountTable(el) { ...@@ -63,7 +63,7 @@ export function mountTable(el) {
<div class="game-tile-content"> <div class="game-tile-content">
<div class="game-tile-icon">${assetImg(g.key + '_icon', g.icon, 48, 48)}</div> <div class="game-tile-icon">${assetImg(g.key + '_icon', g.icon, 48, 48)}</div>
<div class="game-tile-name">${g.name}</div> <div class="game-tile-name">${g.name}</div>
${disabled ? '<div class="game-tile-soon">قريباً</div>' : ''} ${disabled ? `<div class="game-tile-soon">${t('play.coming_soon')}</div>` : ''}
</div> </div>
</div>`; </div>`;
}).join('')} }).join('')}
...@@ -77,32 +77,32 @@ export function mountTable(el) { ...@@ -77,32 +77,32 @@ export function mountTable(el) {
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
height: 100%; height: 100%;
padding: 16px 16px 0; padding: var(--home-padding) var(--home-padding) 0;
overflow-y: auto; overflow-y: auto;
} }
.quick-btn { display:flex;flex-direction:column;align-items:center;gap:4px;flex:1;background:none;border:none;cursor:pointer;padding:6px;transition:transform 0.1s; } .quick-btn { display:flex;flex-direction:column;align-items:center;gap:var(--quick-btn-gap);flex:1;background:none;border:none;cursor:pointer;padding:var(--quick-btn-padding);transition:transform 0.1s; }
.quick-btn:active { transform:scale(0.9); } .quick-btn:active { transform:scale(0.9); }
.qb-icon { width:42px;height:42px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:20px;box-shadow:0 3px 10px rgba(0,0,0,0.3); } .qb-icon { width:var(--quick-btn-icon-size);height:var(--quick-btn-icon-size);border-radius:var(--quick-btn-icon-radius);display:flex;align-items:center;justify-content:center;font-size:var(--quick-btn-icon-font);box-shadow:0 3px 10px rgba(0,0,0,0.3); }
.qb-label { font-size:10px;font-weight:600;color:#94a3b8; } .qb-label { font-size:var(--quick-btn-label-font);font-weight:600;color:var(--text-secondary); }
.games-grid { .games-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 12px; gap: var(--game-tile-gap);
width: 100%; width: 100%;
max-width: 340px; max-width: var(--home-max-width);
} }
.game-tile { .game-tile {
position: relative; position: relative;
aspect-ratio: 1.1; aspect-ratio: var(--game-tile-aspect);
border-radius: 18px; border-radius: var(--game-tile-radius);
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.15s; transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.15s;
box-shadow: 0 4px 16px rgba(0,0,0,0.3); box-shadow: var(--shadow-md);
} }
.game-tile:active { .game-tile:active {
transform: scale(0.93); transform: scale(0.93);
box-shadow: 0 2px 8px rgba(0,0,0,0.4); box-shadow: var(--shadow-sm);
} }
.game-tile-disabled { .game-tile-disabled {
opacity: 0.4; opacity: 0.4;
...@@ -111,12 +111,12 @@ export function mountTable(el) { ...@@ -111,12 +111,12 @@ export function mountTable(el) {
} }
.game-tile-disabled:active { transform: none; } .game-tile-disabled:active { transform: none; }
.game-tile-soon { .game-tile-soon {
font-size: 11px; font-size: var(--gm-btn-sub-font);
font-weight: 700; font-weight: 700;
color: rgba(255,255,255,0.7); color: rgba(255,255,255,0.7);
background: rgba(0,0,0,0.3); background: rgba(0,0,0,0.3);
padding: 2px 10px; padding: 2px 10px;
border-radius: 8px; border-radius: var(--r-sm);
margin-top: 2px; margin-top: 2px;
} }
.game-tile-bg { .game-tile-bg {
...@@ -132,14 +132,15 @@ export function mountTable(el) { ...@@ -132,14 +132,15 @@ export function mountTable(el) {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 100%; height: 100%;
gap: 8px; gap: var(--game-tile-content-gap);
} }
.game-tile-icon { .game-tile-icon {
font-size: 42px; font-size: var(--game-tile-icon-font);
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3)); filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
} }
.game-tile-icon img { width: var(--game-tile-icon-size); height: var(--game-tile-icon-size); object-fit: contain; }
.game-tile-name { .game-tile-name {
font-size: 16px; font-size: var(--game-tile-name-font);
font-weight: 800; font-weight: 800;
color: white; color: white;
text-shadow: 0 1px 3px rgba(0,0,0,0.4); text-shadow: 0 1px 3px rgba(0,0,0,0.4);
...@@ -149,16 +150,16 @@ export function mountTable(el) { ...@@ -149,16 +150,16 @@ export function mountTable(el) {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background: #0f0f1e; background: var(--sheet-bg);
border-top-left-radius: 28px; border-top-left-radius: var(--sheet-radius);
border-top-right-radius: 28px; border-top-right-radius: var(--sheet-radius);
padding: 28px 20px; padding: var(--sheet-padding-top) var(--sheet-padding-x);
padding-bottom: calc(28px + var(--tab-height, 60px) + var(--safe-bottom, 0px)); padding-bottom: calc(var(--sheet-padding-top) + var(--tab-height, 60px) + var(--safe-bottom, 0px));
z-index: 50; z-index: 50;
transform: translateY(0); transform: translateY(0);
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
box-shadow: 0 -8px 40px rgba(0,0,0,0.6); box-shadow: 0 -8px 40px rgba(0,0,0,0.6);
max-height: 75vh; max-height: var(--sheet-max-height);
} }
.game-menu.hidden { .game-menu.hidden {
transform: translateY(100%); transform: translateY(100%);
...@@ -168,78 +169,57 @@ export function mountTable(el) { ...@@ -168,78 +169,57 @@ export function mountTable(el) {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 20px; margin-bottom: var(--menu-header-margin);
} }
/* Game menu buttons */ /* Game menu buttons */
.gm-btn { .gm-btn {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 14px; gap: var(--gm-btn-gap);
width: 100%; width: 100%;
padding: 18px 16px; padding: var(--gm-btn-padding-y) var(--gm-btn-padding-x);
background: #1a1a2e; background: var(--bg-card);
border: 1.5px solid rgba(255,255,255,0.06); border: 1.5px solid var(--border);
border-radius: 16px; border-radius: var(--gm-btn-radius);
cursor: pointer; cursor: pointer;
margin-bottom: 10px; margin-bottom: var(--gm-btn-margin-bottom);
text-align: right; text-align: right;
transition: transform 0.1s, border-color 0.2s; transition: transform 0.1s, border-color 0.2s;
font-family: inherit; font-family: inherit;
} }
.gm-btn:active { transform: scale(0.96); border-color: rgba(255,255,255,0.2); } .gm-btn:active { transform: scale(0.96); border-color: rgba(255,255,255,0.2); }
.gm-btn-icon { .gm-btn-icon {
width: 52px; width: var(--gm-btn-icon-size);
height: 52px; height: var(--gm-btn-icon-size);
border-radius: 14px; border-radius: var(--gm-btn-icon-radius);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 26px; font-size: var(--gm-btn-icon-font);
flex-shrink: 0; flex-shrink: 0;
box-shadow: 0 3px 10px rgba(0,0,0,0.3); box-shadow: 0 3px 10px rgba(0,0,0,0.3);
} }
.gm-btn-body { flex: 1; } .gm-btn-body { flex: 1; }
.gm-btn-title { font-size: 17px; font-weight: 800; color: #f8fafc; } .gm-btn-title { font-size: var(--gm-btn-title-font); font-weight: 800; color: var(--text-primary); }
.gm-btn-sub { font-size: 11px; color: #64748b; margin-top: 2px; } .gm-btn-sub { font-size: var(--gm-btn-sub-font); color: var(--text-muted); margin-top: 2px; }
.gm-btn-arrow { font-size: 22px; color: #475569; font-weight: 300; } .gm-btn-arrow { font-size: var(--gm-btn-arrow-font); color: var(--text-muted); font-weight: 300; }
.gm-chip { .gm-chip {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 4px; gap: var(--gm-chip-gap);
padding: 14px 8px; padding: var(--gm-chip-padding-y) var(--gm-chip-padding-x);
background: #1a1a2e; background: var(--bg-card);
border: 1px solid rgba(255,255,255,0.06); border: 1px solid var(--border);
border-radius: 12px; border-radius: var(--gm-chip-radius);
cursor: pointer; cursor: pointer;
font-family: inherit; font-family: inherit;
color: #e2e8f0; color: var(--text-primary);
font-size: 11px; font-size: var(--gm-chip-font);
font-weight: 600; font-weight: 600;
transition: transform 0.1s, background 0.15s; transition: transform 0.1s, background 0.15s;
} }
.gm-chip:active { transform: scale(0.93); background: rgba(255,255,255,0.08); } .gm-chip:active { transform: scale(0.93); background: rgba(255,255,255,0.08); }
.menu-features {
display: flex;
gap: 10px;
margin-top: 12px;
flex-wrap: wrap;
}
.feature-chip {
padding: 10px 16px;
border-radius: 10px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.08);
color: #e2e8f0;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s, transform 0.1s;
min-height: 40px;
display: flex;
align-items: center;
}
.feature-chip:active { background: rgba(255,255,255,0.1); }
</style> </style>
`; `;
...@@ -290,20 +270,20 @@ function showGameMenu(menu, game) { ...@@ -290,20 +270,20 @@ function showGameMenu(menu, game) {
menu.classList.remove('hidden'); menu.classList.remove('hidden');
menu.style.animation = 'slideUpBounce 0.4s cubic-bezier(0.16, 1, 0.3, 1)'; menu.style.animation = 'slideUpBounce 0.4s cubic-bezier(0.16, 1, 0.3, 1)';
menu.innerHTML = ` menu.innerHTML = `
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;"> <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--menu-header-margin);">
<div style="display:flex;align-items:center;gap:10px;"> <div style="display:flex;align-items:center;gap:var(--menu-header-gap);">
<div style="width:42px;height:42px;border-radius:12px;background:${game.gradient};display:flex;align-items:center;justify-content:center;font-size:22px;">${game.icon}</div> <div style="width:var(--menu-header-icon-size);height:var(--menu-header-icon-size);border-radius:var(--menu-header-icon-radius);background:${game.gradient};display:flex;align-items:center;justify-content:center;font-size:var(--menu-header-icon-font);">${game.icon}</div>
<div style="font-size:22px;font-weight:800;color:#f8fafc;">${game.name}</div> <div style="font-size:var(--menu-header-title-font);font-weight:800;color:var(--text-primary);">${game.name}</div>
</div> </div>
<button id="menu-close" style="width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,0.06);border:none;color:#94a3b8;font-size:20px;cursor:pointer;display:flex;align-items:center;justify-content:center;">✕</button> <button id="menu-close" style="width:var(--menu-close-size);height:var(--menu-close-size);border-radius:50%;background:var(--border);border:none;color:var(--text-secondary);font-size:var(--menu-close-font);cursor:pointer;display:flex;align-items:center;justify-content:center;">✕</button>
</div> </div>
<!-- Main play buttons --> <!-- Main play buttons -->
<button class="gm-btn" id="btn-single"> <button class="gm-btn" id="btn-single">
<div class="gm-btn-icon" style="background:${game.gradient};">${emoji('gamepad', '🎮', 26)}</div> <div class="gm-btn-icon" style="background:${game.gradient};">${emoji('gamepad', '🎮', 26)}</div>
<div class="gm-btn-body"> <div class="gm-btn-body">
<div class="gm-btn-title">لاعب واحد</div> <div class="gm-btn-title">${t('play.single_player')}</div>
<div class="gm-btn-sub">العب ضد الكمبيوتر</div> <div class="gm-btn-sub">${t('play.single_player_desc')}</div>
</div> </div>
<div class="gm-btn-arrow">›</div> <div class="gm-btn-arrow">›</div>
</button> </button>
...@@ -311,25 +291,25 @@ function showGameMenu(menu, game) { ...@@ -311,25 +291,25 @@ function showGameMenu(menu, game) {
<button class="gm-btn" id="btn-multi"> <button class="gm-btn" id="btn-multi">
<div class="gm-btn-icon" style="background:linear-gradient(135deg,#dc2626,#f97316);">${emoji('swords', '⚔️', 26)}</div> <div class="gm-btn-icon" style="background:linear-gradient(135deg,#dc2626,#f97316);">${emoji('swords', '⚔️', 26)}</div>
<div class="gm-btn-body"> <div class="gm-btn-body">
<div class="gm-btn-title">أونلاين</div> <div class="gm-btn-title">${t('game.online')}</div>
<div class="gm-btn-sub">نافس لاعبين حقيقيين</div> <div class="gm-btn-sub">${t('play.online_desc')}</div>
</div> </div>
<div class="gm-btn-arrow">›</div> <div class="gm-btn-arrow">›</div>
</button> </button>
<!-- Secondary actions --> <!-- Secondary actions -->
<div style="display:grid;grid-template-columns:${game.key === 'chess' ? '1fr 1fr 1fr' : '1fr 1fr'};gap:8px;margin-top:14px;"> <div style="display:grid;grid-template-columns:${game.key === 'chess' ? '1fr 1fr 1fr' : '1fr 1fr'};gap:var(--gm-chips-grid-gap);margin-top:var(--gm-chips-margin-top);">
<button class="gm-chip" id="btn-leaderboard"> <button class="gm-chip" id="btn-leaderboard">
<span style="font-size:18px;">${emoji('tournament_cup', '🏆', 18)}</span> <span style="font-size:var(--gm-chip-icon-font);">${emoji('tournament_cup', '🏆', 18)}</span>
<span>الترتيب</span> <span>Leaderboard</span>
</button> </button>
<button class="gm-chip" id="btn-history"> <button class="gm-chip" id="btn-history">
<span style="font-size:18px;">${emoji('clipboard', '📋', 18)}</span> <span style="font-size:var(--gm-chip-icon-font);">${emoji('clipboard', '📋', 18)}</span>
<span>مبارياتي</span> <span>My Games</span>
</button> </button>
${game.key === 'chess' ? `<button class="gm-chip" id="btn-puzzles"> ${game.key === 'chess' ? `<button class="gm-chip" id="btn-puzzles">
<span style="font-size:18px;">${emoji('puzzle', '🧩', 18)}</span> <span style="font-size:var(--gm-chip-icon-font);">${emoji('puzzle', '🧩', 18)}</span>
<span>أحجيات</span> <span>Puzzles</span>
</button>` : ''} </button>` : ''}
</div> </div>
`; `;
...@@ -402,7 +382,7 @@ function showGameMenu(menu, game) { ...@@ -402,7 +382,7 @@ function showGameMenu(menu, game) {
menu.querySelector('#btn-history')?.addEventListener('click', () => { menu.querySelector('#btn-history')?.addEventListener('click', () => {
audio.play('click'); audio.play('click');
menu.classList.add('hidden'); menu.classList.add('hidden');
scene.push('chess-history'); scene.push('chess-history', { game: game.key });
}); });
menu.querySelector('#btn-puzzles')?.addEventListener('click', () => { menu.querySelector('#btn-puzzles')?.addEventListener('click', () => {
......
...@@ -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>
......
...@@ -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 bus from '../../../core/bus.js'; import * as bus from '../../../core/bus.js';
import * as net from '../../../core/net.js'; import * as net from '../../../core/net.js';
import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
const COUNTRIES = [ const COUNTRIES = [
...@@ -50,31 +51,31 @@ export async function mountEdit(el) { ...@@ -50,31 +51,31 @@ export async function mountEdit(el) {
<button id="btn-back" class="btn btn-secondary" style="min-width:40px;min-height:40px;padding:0;display:flex;align-items:center;justify-content:center;"> <button id="btn-back" class="btn btn-secondary" style="min-width:40px;min-height:40px;padding:0;display:flex;align-items:center;justify-content:center;">
${emoji('arrow_back', '←', 18)} ${emoji('arrow_back', '←', 18)}
</button> </button>
<div style="font-size:18px;font-weight:700;">تعديل الملف الشخصي</div> <div style="font-size:18px;font-weight:700;">${t('profile.edit_title')}</div>
</div> </div>
<!-- Section: Basic Info --> <!-- Section: Basic Info -->
<div class="card"> <div class="card">
<div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${emoji('person', '👤', 18)} المعلومات الأساسية</div> <div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${emoji('person', '👤', 18)} ${t('profile.basic_info')}</div>
<div style="display:flex;flex-direction:column;gap:var(--s-3);"> <div style="display:flex;flex-direction:column;gap:var(--s-3);">
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">الاسم المعروض (إنجليزي)</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.display_name_en')}</label>
<input id="field-display_name" type="text" maxlength="30" dir="auto" value="${escAttr(player.display_name || '')}" <input id="field-display_name" type="text" maxlength="30" dir="auto" value="${escAttr(player.display_name || '')}"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">الاسم المعروض (عربي)</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.display_name_ar')}</label>
<input id="field-display_name_ar" type="text" maxlength="30" dir="rtl" value="${escAttr(player.display_name_ar || '')}" <input id="field-display_name_ar" type="text" maxlength="30" dir="rtl" value="${escAttr(player.display_name_ar || '')}"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">النبذة (إنجليزي)</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.bio_en')}</label>
<textarea id="field-bio" maxlength="200" dir="auto" rows="3" <textarea id="field-bio" maxlength="200" dir="auto" rows="3"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;resize:vertical;">${escHtml(player.bio || '')}</textarea> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;resize:vertical;">${escHtml(player.bio || '')}</textarea>
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">النبذة (عربي)</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.bio_ar')}</label>
<textarea id="field-bio_ar" maxlength="200" dir="rtl" rows="3" <textarea id="field-bio_ar" maxlength="200" dir="rtl" rows="3"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;resize:vertical;">${escHtml(player.bio_ar || '')}</textarea> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;resize:vertical;">${escHtml(player.bio_ar || '')}</textarea>
</div> </div>
...@@ -83,24 +84,24 @@ export async function mountEdit(el) { ...@@ -83,24 +84,24 @@ export async function mountEdit(el) {
<!-- Section: Location --> <!-- Section: Location -->
<div class="card"> <div class="card">
<div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${emoji('globe', '🌍', 18)} الموقع</div> <div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${emoji('globe', '🌍', 18)} ${t('profile.location')}</div>
<div style="display:flex;flex-direction:column;gap:var(--s-3);"> <div style="display:flex;flex-direction:column;gap:var(--s-3);">
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">الدولة</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.country')}</label>
<select id="field-country_code" <select id="field-country_code"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
<option value="">اختر الدولة</option> <option value="">${t('profile.select_country')}</option>
${COUNTRIES.map(c => `<option value="${c.code}" ${player.country_code === c.code ? 'selected' : ''}>${c.name}</option>`).join('')} ${COUNTRIES.map(c => `<option value="${c.code}" ${player.country_code === c.code ? 'selected' : ''}>${c.name}</option>`).join('')}
</select> </select>
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">المدينة</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.city')}</label>
<input id="field-city" type="text" dir="auto" value="${escAttr(player.city || '')}" <input id="field-city" type="text" dir="auto" value="${escAttr(player.city || '')}"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">اللغة المفضلة</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.preferred_language')}</label>
<div id="lang-toggle" style="display:flex;gap:var(--s-2);"> <div id="lang-toggle" style="display:flex;gap:var(--s-2);">
<button class="btn lang-opt ${(player.preferred_language || 'ar') === 'ar' ? 'btn-primary' : 'btn-secondary'}" data-lang="ar" style="flex:1;min-height:40px;">العربية</button> <button class="btn lang-opt ${(player.preferred_language || 'ar') === 'ar' ? 'btn-primary' : 'btn-secondary'}" data-lang="ar" style="flex:1;min-height:40px;">العربية</button>
<button class="btn lang-opt ${player.preferred_language === 'en' ? 'btn-primary' : 'btn-secondary'}" data-lang="en" style="flex:1;min-height:40px;">English</button> <button class="btn lang-opt ${player.preferred_language === 'en' ? 'btn-primary' : 'btn-secondary'}" data-lang="en" style="flex:1;min-height:40px;">English</button>
...@@ -111,7 +112,7 @@ export async function mountEdit(el) { ...@@ -111,7 +112,7 @@ export async function mountEdit(el) {
<!-- Section: FIDE Info --> <!-- Section: FIDE Info -->
<div class="card"> <div class="card">
<div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${emoji('trophy', '🏆', 18)} معلومات FIDE</div> <div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${emoji('trophy', '🏆', 18)} ${t('profile.fide_info')}</div>
<div style="display:flex;flex-direction:column;gap:var(--s-3);"> <div style="display:flex;flex-direction:column;gap:var(--s-3);">
<div> <div>
...@@ -120,25 +121,25 @@ export async function mountEdit(el) { ...@@ -120,25 +121,25 @@ export async function mountEdit(el) {
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">تصنيف FIDE الكلاسيكي</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.fide_classical')}</label>
<input id="field-fide_rating_standard" type="number" inputmode="numeric" dir="ltr" min="0" max="4000" value="${player.fide_rating_standard || ''}" <input id="field-fide_rating_standard" type="number" inputmode="numeric" dir="ltr" min="0" max="4000" value="${player.fide_rating_standard || ''}"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">تصنيف FIDE السريع</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.fide_rapid')}</label>
<input id="field-fide_rating_rapid" type="number" inputmode="numeric" dir="ltr" min="0" max="4000" value="${player.fide_rating_rapid || ''}" <input id="field-fide_rating_rapid" type="number" inputmode="numeric" dir="ltr" min="0" max="4000" value="${player.fide_rating_rapid || ''}"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">تصنيف FIDE الخاطف</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.fide_blitz')}</label>
<input id="field-fide_rating_blitz" type="number" inputmode="numeric" dir="ltr" min="0" max="4000" value="${player.fide_rating_blitz || ''}" <input id="field-fide_rating_blitz" type="number" inputmode="numeric" dir="ltr" min="0" max="4000" value="${player.fide_rating_blitz || ''}"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">لقب FIDE</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('profile.fide_title')}</label>
<select id="field-fide_title" <select id="field-fide_title"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
${FIDE_TITLES.map(t => `<option value="${t.value}" ${player.fide_title === t.value ? 'selected' : ''}>${t.label}</option>`).join('')} ${FIDE_TITLES.map(ft => `<option value="${ft.value}" ${player.fide_title === ft.value ? 'selected' : ''}>${ft.label}</option>`).join('')}
</select> </select>
</div> </div>
</div> </div>
...@@ -146,7 +147,7 @@ export async function mountEdit(el) { ...@@ -146,7 +147,7 @@ export async function mountEdit(el) {
<!-- Save Button --> <!-- Save Button -->
<button id="btn-save" class="btn btn-primary" style="width:100%;min-height:48px;font-size:16px;font-weight:700;"> <button id="btn-save" class="btn btn-primary" style="width:100%;min-height:48px;font-size:16px;font-weight:700;">
${emoji('save', '💾', 18)} حفظ التغييرات ${emoji('save', '💾', 18)} ${t('profile.save')}
</button> </button>
</div> </div>
`; `;
...@@ -178,7 +179,7 @@ export async function mountEdit(el) { ...@@ -178,7 +179,7 @@ export async function mountEdit(el) {
const btn = el.querySelector('#btn-save'); const btn = el.querySelector('#btn-save');
btn.disabled = true; btn.disabled = true;
btn.style.opacity = '0.6'; btn.style.opacity = '0.6';
btn.textContent = 'جاري الحفظ...'; btn.textContent = t('profile.saving');
const data = { const data = {
display_name: el.querySelector('#field-display_name').value.trim(), display_name: el.querySelector('#field-display_name').value.trim(),
...@@ -206,15 +207,15 @@ export async function mountEdit(el) { ...@@ -206,15 +207,15 @@ export async function mountEdit(el) {
const updated = { ...store.get('player'), ...data }; const updated = { ...store.get('player'), ...data };
store.set('player', updated); store.set('player', updated);
bus.emit('store:player'); bus.emit('store:player');
bus.emit('toast', { text: 'تم حفظ التغييرات بنجاح', type: 'success' }); bus.emit('toast', { text: t('profile.save_success'), type: 'success' });
scene.pop(); scene.pop();
} catch (err) { } catch (err) {
bus.emit('toast', { text: err.message || 'فشل حفظ التغييرات', type: 'error' }); bus.emit('toast', { text: err.message || t('profile.save_failed'), type: 'error' });
} }
btn.disabled = false; btn.disabled = false;
btn.style.opacity = '1'; btn.style.opacity = '1';
btn.innerHTML = `${emoji('save', '💾', 18)} حفظ التغييرات`; btn.innerHTML = `${emoji('save', '💾', 18)} ${t('profile.save')}`;
}); });
} }
......
...@@ -3,13 +3,14 @@ import * as scene from '../../../core/scene.js'; ...@@ -3,13 +3,14 @@ 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 * as net from '../../../core/net.js'; import * as net from '../../../core/net.js';
import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
const DOC_TYPES = [ const DOC_TYPES = [
{ value: 'membership_card', label: 'بطاقة عضوية' }, { value: 'membership_card', get label() { return t('org.doc_membership'); } },
{ value: 'id_card', label: 'بطاقة هوية' }, { value: 'id_card', get label() { return t('org.doc_id'); } },
{ value: 'receipt', label: 'إيصال دفع' }, { value: 'receipt', get label() { return t('org.doc_receipt'); } },
{ value: 'other', label: 'أخرى' } { value: 'other', get label() { return t('org.doc_other'); } }
]; ];
export async function mountOrgApply(el) { export async function mountOrgApply(el) {
...@@ -19,10 +20,10 @@ export async function mountOrgApply(el) { ...@@ -19,10 +20,10 @@ export async function mountOrgApply(el) {
<button id="btn-back" class="btn btn-secondary" style="min-width:40px;min-height:40px;padding:0;display:flex;align-items:center;justify-content:center;"> <button id="btn-back" class="btn btn-secondary" style="min-width:40px;min-height:40px;padding:0;display:flex;align-items:center;justify-content:center;">
${emoji('arrow_back', '←', 18)} ${emoji('arrow_back', '←', 18)}
</button> </button>
<div style="font-size:18px;font-weight:700;">الانضمام لمنظمة</div> <div style="font-size:18px;font-weight:700;">${t('org.join_title')}</div>
</div> </div>
<div id="org-content" style="display:flex;flex-direction:column;gap:var(--s-3);"> <div id="org-content" style="display:flex;flex-direction:column;gap:var(--s-3);">
<div style="text-align:center;padding:var(--s-6);color:var(--text-secondary);">جاري التحميل...</div> <div style="text-align:center;padding:var(--s-6);color:var(--text-secondary);">${t('common.loading')}</div>
</div> </div>
</div> </div>
`; `;
...@@ -46,7 +47,7 @@ async function loadOrgs(el) { ...@@ -46,7 +47,7 @@ async function loadOrgs(el) {
]); ]);
if (!orgs || !Array.isArray(orgs) || orgs.length === 0) { if (!orgs || !Array.isArray(orgs) || orgs.length === 0) {
content.innerHTML = `<div class="card" style="text-align:center;padding:var(--s-6);color:var(--text-secondary);">لا توجد منظمات متاحة حالياً</div>`; content.innerHTML = `<div class="card" style="text-align:center;padding:var(--s-6);color:var(--text-secondary);">${t('org.no_orgs')}</div>`;
return; return;
} }
...@@ -70,17 +71,17 @@ async function loadOrgs(el) { ...@@ -70,17 +71,17 @@ async function loadOrgs(el) {
let actionBtn = ''; let actionBtn = '';
if (isMember) { if (isMember) {
badge = `<span style="background:var(--success);color:#fff;font-size:11px;padding:2px 8px;border-radius:12px;font-weight:600;">${emoji('check', '✓', 12)} عضو</span>`; badge = `<span style="background:var(--success);color:#fff;font-size:11px;padding:2px 8px;border-radius:12px;font-weight:600;">${emoji('check', '✓', 12)} ${t('profile.member')}</span>`;
} else if (app && app.status === 'pending') { } else if (app && app.status === 'pending') {
badge = `<span style="background:#f59e0b;color:#000;font-size:11px;padding:2px 8px;border-radius:12px;font-weight:600;">قيد المراجعة</span>`; badge = `<span style="background:#f59e0b;color:#000;font-size:11px;padding:2px 8px;border-radius:12px;font-weight:600;">${t('profile.pending_review')}</span>`;
} else if (app && app.status === 'rejected') { } else if (app && app.status === 'rejected') {
badge = `<span style="background:var(--error);color:#fff;font-size:11px;padding:2px 8px;border-radius:12px;font-weight:600;">مرفوض</span>`; badge = `<span style="background:var(--error);color:#fff;font-size:11px;padding:2px 8px;border-radius:12px;font-weight:600;">${t('profile.rejected')}</span>`;
if (app.rejection_reason) { if (app.rejection_reason) {
badge += `<div style="font-size:11px;color:var(--error);margin-top:4px;">السبب: ${escHtml(app.rejection_reason)}</div>`; badge += `<div style="font-size:11px;color:var(--error);margin-top:4px;">${t('profile.rejection_reason', { reason: escHtml(app.rejection_reason) })}</div>`;
} }
actionBtn = `<button class="btn btn-primary btn-apply" data-org-id="${org.id}" style="min-height:36px;font-size:13px;margin-top:var(--s-2);">إعادة التقديم</button>`; actionBtn = `<button class="btn btn-primary btn-apply" data-org-id="${org.id}" style="min-height:36px;font-size:13px;margin-top:var(--s-2);">${t('org.reapply')}</button>`;
} else { } else {
actionBtn = `<button class="btn btn-primary btn-apply" data-org-id="${org.id}" style="min-height:36px;font-size:13px;margin-top:var(--s-2);">تقديم طلب</button>`; actionBtn = `<button class="btn btn-primary btn-apply" data-org-id="${org.id}" style="min-height:36px;font-size:13px;margin-top:var(--s-2);">${t('org.apply')}</button>`;
} }
const logoHtml = org.logo_url const logoHtml = org.logo_url
...@@ -113,7 +114,7 @@ async function loadOrgs(el) { ...@@ -113,7 +114,7 @@ async function loadOrgs(el) {
}); });
} catch (err) { } catch (err) {
content.innerHTML = `<div class="card" style="text-align:center;padding:var(--s-4);color:var(--error);">فشل تحميل المنظمات: ${escHtml(err.message)}</div>`; content.innerHTML = `<div class="card" style="text-align:center;padding:var(--s-4);color:var(--error);">${t('common.error_load')}</div>`;
} }
} }
...@@ -122,11 +123,11 @@ function showApplyForm(el, org, contentParent) { ...@@ -122,11 +123,11 @@ function showApplyForm(el, org, contentParent) {
content.innerHTML = ` content.innerHTML = `
<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);">تقديم طلب انضمام — ${escHtml(org.name)}</div> <div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${t('org.apply_title', { name: escHtml(org.name) })}</div>
<div style="display:flex;flex-direction:column;gap:var(--s-3);"> <div style="display:flex;flex-direction:column;gap:var(--s-3);">
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">نوع المستند</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('org.doc_type')}</label>
<select id="apply-doc-type" <select id="apply-doc-type"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;"> style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;">
${DOC_TYPES.map(d => `<option value="${d.value}">${d.label}</option>`).join('')} ${DOC_TYPES.map(d => `<option value="${d.value}">${d.label}</option>`).join('')}
...@@ -134,19 +135,19 @@ function showApplyForm(el, org, contentParent) { ...@@ -134,19 +135,19 @@ function showApplyForm(el, org, contentParent) {
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">ملاحظات (اختياري)</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('org.notes')}</label>
<textarea id="apply-notes" maxlength="500" dir="auto" rows="3" <textarea id="apply-notes" maxlength="500" dir="auto" rows="3"
style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;resize:vertical;" style="background:#1e1e3a;border:1px solid rgba(255,255,255,0.1);color:#f8fafc;border-radius:8px;padding:10px 12px;font-size:14px;font-family:inherit;width:100%;box-sizing:border-box;resize:vertical;"
placeholder="رسالة لإدارة المنظمة..."></textarea> placeholder="${t('org.notes_placeholder')}"></textarea>
</div> </div>
<div> <div>
<label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">صورة إثبات</label> <label style="font-size:13px;color:var(--text-secondary);margin-bottom:4px;display:block;">${t('org.proof_image')}</label>
<div id="upload-area" style="background:#1e1e3a;border:2px dashed rgba(255,255,255,0.15);border-radius:8px;padding:var(--s-4);text-align:center;cursor:pointer;"> <div id="upload-area" style="background:#1e1e3a;border:2px dashed rgba(255,255,255,0.15);border-radius:8px;padding:var(--s-4);text-align:center;cursor:pointer;">
<div id="upload-placeholder"> <div id="upload-placeholder">
<div>${emoji('camera', '📷', 28)}</div> <div>${emoji('camera', '📷', 28)}</div>
<div style="font-size:13px;color:var(--text-secondary);margin-top:var(--s-2);">اضغط لاختيار صورة</div> <div style="font-size:13px;color:var(--text-secondary);margin-top:var(--s-2);">${t('org.tap_to_select')}</div>
<div style="font-size:11px;color:var(--text-secondary);">PNG, JPG, WebP — حد أقصى 5MB</div> <div style="font-size:11px;color:var(--text-secondary);">${t('org.file_limit')}</div>
</div> </div>
<div id="upload-preview" style="display:none;"> <div id="upload-preview" style="display:none;">
<img id="preview-img" style="max-width:100%;max-height:200px;border-radius:6px;object-fit:contain;"> <img id="preview-img" style="max-width:100%;max-height:200px;border-radius:6px;object-fit:contain;">
...@@ -156,8 +157,8 @@ function showApplyForm(el, org, contentParent) { ...@@ -156,8 +157,8 @@ function showApplyForm(el, org, contentParent) {
</div> </div>
<div style="display:flex;gap:var(--s-3);margin-top:var(--s-2);"> <div style="display:flex;gap:var(--s-3);margin-top:var(--s-2);">
<button id="btn-cancel-apply" class="btn btn-secondary" style="flex:1;min-height:44px;">إلغاء</button> <button id="btn-cancel-apply" class="btn btn-secondary" style="flex:1;min-height:44px;">${t('common.cancel')}</button>
<button id="btn-submit-apply" class="btn btn-primary" style="flex:1;min-height:44px;font-weight:700;">إرسال الطلب</button> <button id="btn-submit-apply" class="btn btn-primary" style="flex:1;min-height:44px;font-weight:700;">${t('org.submit')}</button>
</div> </div>
</div> </div>
</div> </div>
...@@ -177,7 +178,7 @@ function showApplyForm(el, org, contentParent) { ...@@ -177,7 +178,7 @@ function showApplyForm(el, org, contentParent) {
const file = e.target.files[0]; const file = e.target.files[0];
if (!file) return; if (!file) return;
if (file.size > 5 * 1024 * 1024) { if (file.size > 5 * 1024 * 1024) {
bus.emit('toast', { text: 'حجم الملف أكبر من 5MB', type: 'error' }); bus.emit('toast', { text: t('org.file_too_large'), type: 'error' });
return; return;
} }
selectedFile = file; selectedFile = file;
...@@ -206,7 +207,7 @@ function showApplyForm(el, org, contentParent) { ...@@ -206,7 +207,7 @@ function showApplyForm(el, org, contentParent) {
btn.disabled = true; btn.disabled = true;
btn.style.opacity = '0.6'; btn.style.opacity = '0.6';
btn.textContent = 'جاري الإرسال...'; btn.textContent = t('org.submitting');
try { try {
const formData = new FormData(); const formData = new FormData();
...@@ -228,16 +229,16 @@ function showApplyForm(el, org, contentParent) { ...@@ -228,16 +229,16 @@ function showApplyForm(el, org, contentParent) {
const data = await res.json(); const data = await res.json();
if (!res.ok || data.error) { if (!res.ok || data.error) {
throw new Error(data.error || 'فشل إرسال الطلب'); throw new Error(data.error || t('org.submit_failed'));
} }
bus.emit('toast', { text: 'تم إرسال الطلب بنجاح', type: 'success' }); bus.emit('toast', { text: t('org.submit_success'), type: 'success' });
await loadOrgs(el); await loadOrgs(el);
} catch (err) { } catch (err) {
bus.emit('toast', { text: err.message || 'فشل إرسال الطلب', type: 'error' }); bus.emit('toast', { text: err.message || t('org.submit_failed'), type: 'error' });
btn.disabled = false; btn.disabled = false;
btn.style.opacity = '1'; btn.style.opacity = '1';
btn.textContent = 'إرسال الطلب'; btn.textContent = t('org.submit');
} }
}); });
} }
......
...@@ -38,7 +38,7 @@ async function mountOwnProfile(el) { ...@@ -38,7 +38,7 @@ async function mountOwnProfile(el) {
<div class="profile-avatar-edit">${emoji('camera', '📷', 14)}</div> <div class="profile-avatar-edit">${emoji('camera', '📷', 14)}</div>
</div> </div>
<input type="file" id="avatar-input" accept="image/*" style="display:none;"> <input type="file" id="avatar-input" accept="image/*" style="display:none;">
<div id="avatar-uploading" style="display:none;margin:var(--s-2) auto 0;font-size:12px;color:var(--text-secondary);">جاري الرفع...</div> <div id="avatar-uploading" style="display:none;margin:var(--s-2) auto 0;font-size:12px;color:var(--text-secondary);">${t('profile.uploading')}</div>
<div style="font-size:18px;font-weight:700;margin-top:var(--s-3);">${player.display_name || player.username || 'Player'}</div> <div style="font-size:18px;font-weight:700;margin-top:var(--s-3);">${player.display_name || player.username || 'Player'}</div>
<div style="font-size:13px;color:var(--text-secondary);margin-top:2px;">Level ${player.level || 1}</div> <div style="font-size:13px;color:var(--text-secondary);margin-top:2px;">Level ${player.level || 1}</div>
</div> </div>
...@@ -49,45 +49,45 @@ async function mountOwnProfile(el) { ...@@ -49,45 +49,45 @@ async function mountOwnProfile(el) {
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s-3);text-align:center;"> <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s-3);text-align:center;">
<div> <div>
<div style="font-size:20px;font-weight:700;color:var(--gold);font-family:var(--font-lat);">${player.games_played || 0}</div> <div style="font-size:20px;font-weight:700;color:var(--gold);font-family:var(--font-lat);">${player.games_played || 0}</div>
<div style="font-size:11px;color:var(--text-secondary);">مباريات</div> <div style="font-size:11px;color:var(--text-secondary);">${t('profile.matches')}</div>
</div> </div>
<div> <div>
<div style="font-size:20px;font-weight:700;color:var(--win);font-family:var(--font-lat);">${player.games_won || 0}</div> <div style="font-size:20px;font-weight:700;color:var(--win);font-family:var(--font-lat);">${player.games_won || 0}</div>
<div style="font-size:11px;color:var(--text-secondary);">فوز</div> <div style="font-size:11px;color:var(--text-secondary);">${t('profile.wins')}</div>
</div> </div>
<div> <div>
<div style="font-size:20px;font-weight:700;color:var(--cyan);font-family:var(--font-lat);">${player.current_streak || 0}</div> <div style="font-size:20px;font-weight:700;color:var(--cyan);font-family:var(--font-lat);">${player.current_streak || 0}</div>
<div style="font-size:11px;color:var(--text-secondary);">سلسلة</div> <div style="font-size:11px;color:var(--text-secondary);">${t('profile.streak')}</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Ratings --> <!-- Ratings -->
<div class="card"> <div class="card">
<div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">التصنيف</div> <div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${t('profile.rating_section')}</div>
<div style="display:flex;flex-direction:column;gap:var(--s-2);"> <div style="display:flex;flex-direction:column;gap:var(--s-2);">
${renderRating('شطرنج (سريع)', player.elo_rapid)} ${renderRating(t('profile.chess_rapid'), player.elo_rapid)}
${renderRating('شطرنج (خاطف)', player.elo_blitz)} ${renderRating(t('profile.chess_blitz'), player.elo_blitz)}
${renderRating('شطرنج (رصاصة)', player.elo_bullet)} ${renderRating(t('profile.chess_bullet'), player.elo_bullet)}
</div> </div>
</div> </div>
<!-- Organization Membership --> <!-- Organization Membership -->
<div class="card" id="org-section"> <div class="card" id="org-section">
<div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${emoji('building', '🏢', 18)} المنظمة</div> <div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${emoji('building', '🏢', 18)} ${t('profile.org_section')}</div>
<div id="org-membership-content" style="font-size:13px;color:var(--text-secondary);">جاري التحميل...</div> <div id="org-membership-content" style="font-size:13px;color:var(--text-secondary);">${t('common.loading')}</div>
</div> </div>
<!-- Profile Actions --> <!-- Profile Actions -->
<div style="display:flex;flex-direction:column;gap:var(--s-3);"> <div style="display:flex;flex-direction:column;gap:var(--s-3);">
<button class="btn btn-primary w-full" id="btn-edit-profile" style="min-height:44px;">${emoji('edit', '✏️', 16)} تعديل الملف الشخصي</button> <button class="btn btn-primary w-full" id="btn-edit-profile" style="min-height:44px;">${emoji('edit', '✏️', 16)} ${t('profile.edit_btn')}</button>
<button class="btn btn-secondary w-full" id="btn-org-apply" style="min-height:44px;">${emoji('building', '🏢', 16)} الانضمام لمنظمة</button> <button class="btn btn-secondary w-full" id="btn-org-apply" style="min-height:44px;">${emoji('building', '🏢', 16)} ${t('profile.org_apply')}</button>
</div> </div>
<!-- Actions --> <!-- Actions -->
<div style="display:flex;gap:var(--s-3);"> <div style="display:flex;gap:var(--s-3);">
<button class="btn btn-secondary w-full" id="btn-settings">${t('profile.settings')}</button> <button class="btn btn-secondary w-full" id="btn-settings">${t('profile.settings')}</button>
<button class="btn btn-secondary w-full" id="btn-logout" style="color:var(--error);">خروج</button> <button class="btn btn-secondary w-full" id="btn-logout" style="color:var(--error);">${t('profile.logout')}</button>
</div> </div>
</div> </div>
`; `;
...@@ -126,10 +126,10 @@ async function mountOwnProfile(el) { ...@@ -126,10 +126,10 @@ async function mountOwnProfile(el) {
mountView(el); mountView(el);
return; return;
} else { } else {
bus.emit('toast', { text: data.error || 'فشل رفع الصورة', type: 'error' }); bus.emit('toast', { text: data.error || t('profile.upload_failed'), type: 'error' });
} }
} catch (err) { } catch (err) {
bus.emit('toast', { text: 'فشل رفع الصورة', type: 'error' }); bus.emit('toast', { text: t('profile.upload_failed'), type: 'error' });
} }
isUploading = false; isUploading = false;
avatarWrap.style.opacity = '1'; avatarWrap.style.opacity = '1';
...@@ -192,7 +192,7 @@ async function mountOtherProfile(el, playerId) { ...@@ -192,7 +192,7 @@ async function mountOtherProfile(el, playerId) {
<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);">
<div style="display:flex;align-items:center;gap:var(--s-3);"> <div style="display:flex;align-items:center;gap:var(--s-3);">
<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;flex:1;">الملف الشخصي</h2> <h2 style="font-size:18px;font-weight:700;flex:1;">${t('profile.view_title')}</h2>
</div> </div>
<!-- Player Card --> <!-- Player Card -->
...@@ -212,26 +212,26 @@ async function mountOtherProfile(el, playerId) { ...@@ -212,26 +212,26 @@ async function mountOtherProfile(el, playerId) {
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s-3);text-align:center;"> <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s-3);text-align:center;">
<div> <div>
<div style="font-size:20px;font-weight:700;color:var(--gold);font-family:var(--font-lat);">${player.games_played || 0}</div> <div style="font-size:20px;font-weight:700;color:var(--gold);font-family:var(--font-lat);">${player.games_played || 0}</div>
<div style="font-size:11px;color:var(--text-secondary);">مباريات</div> <div style="font-size:11px;color:var(--text-secondary);">${t('profile.matches')}</div>
</div> </div>
<div> <div>
<div style="font-size:20px;font-weight:700;color:var(--win);font-family:var(--font-lat);">${player.games_won || 0}</div> <div style="font-size:20px;font-weight:700;color:var(--win);font-family:var(--font-lat);">${player.games_won || 0}</div>
<div style="font-size:11px;color:var(--text-secondary);">فوز</div> <div style="font-size:11px;color:var(--text-secondary);">${t('profile.wins')}</div>
</div> </div>
<div> <div>
<div style="font-size:20px;font-weight:700;color:var(--cyan);font-family:var(--font-lat);">${player.current_streak || 0}</div> <div style="font-size:20px;font-weight:700;color:var(--cyan);font-family:var(--font-lat);">${player.current_streak || 0}</div>
<div style="font-size:11px;color:var(--text-secondary);">سلسلة</div> <div style="font-size:11px;color:var(--text-secondary);">${t('profile.streak')}</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Ratings --> <!-- Ratings -->
<div class="card"> <div class="card">
<div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">التصنيف</div> <div style="font-size:15px;font-weight:600;margin-bottom:var(--s-3);">${t('profile.rating_section')}</div>
<div style="display:flex;flex-direction:column;gap:var(--s-2);"> <div style="display:flex;flex-direction:column;gap:var(--s-2);">
${renderRating('شطرنج (سريع)', player.elo_rapid)} ${renderRating(t('profile.chess_rapid'), player.elo_rapid)}
${renderRating('شطرنج (خاطف)', player.elo_blitz)} ${renderRating(t('profile.chess_blitz'), player.elo_blitz)}
${renderRating('شطرنج (رصاصة)', player.elo_bullet)} ${renderRating(t('profile.chess_bullet'), player.elo_bullet)}
</div> </div>
</div> </div>
...@@ -255,7 +255,7 @@ async function mountOtherProfile(el, playerId) { ...@@ -255,7 +255,7 @@ async function mountOtherProfile(el, playerId) {
addFriendBtn.style.opacity = '0.5'; addFriendBtn.style.opacity = '0.5';
try { try {
await net.post('friends.php', { action: 'request', target_id: playerId }); await net.post('friends.php', { action: 'request', target_id: playerId });
addFriendBtn.textContent = '✓ تم إرسال الطلب'; addFriendBtn.textContent = t('profile.friend_sent');
addFriendBtn.style.color = 'var(--success)'; addFriendBtn.style.color = 'var(--success)';
} catch (e) { } catch (e) {
addFriendBtn.textContent = e.message || t('common.error'); addFriendBtn.textContent = e.message || t('common.error');
...@@ -285,10 +285,10 @@ async function mountOtherProfile(el, playerId) { ...@@ -285,10 +285,10 @@ async function mountOtherProfile(el, playerId) {
if (blockBtn) { if (blockBtn) {
blockBtn.addEventListener('click', async () => { blockBtn.addEventListener('click', async () => {
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('mp.block'),
cancelText: 'إلغاء', cancelText: t('common.cancel'),
danger: true danger: true
}); });
if (!confirmed) return; if (!confirmed) return;
...@@ -337,14 +337,14 @@ async function checkActiveMatch(el, playerId) { ...@@ -337,14 +337,14 @@ async function checkActiveMatch(el, playerId) {
const data = await net.post('game.php', { action: 'find-active-match', player_id: playerId }); const data = await net.post('game.php', { action: 'find-active-match', player_id: playerId });
if (data.match_id) { if (data.match_id) {
section.style.display = 'block'; section.style.display = 'block';
const gameLabel = data.game_key === 'ludo' ? 'لودو' : data.game_key === 'domino' ? 'دومينو' : 'شطرنج'; const gameLabel = data.game_key === 'ludo' ? t('game.ludo') : data.game_key === 'domino' ? t('game.domino') : t('game.chess');
section.innerHTML = ` section.innerHTML = `
<div class="card" style="background:linear-gradient(135deg,#1a2a1a,#0f1f0f);border:1px solid rgba(52,211,153,0.3);padding:var(--s-3);display:flex;align-items:center;gap:var(--s-3);"> <div class="card" style="background:linear-gradient(135deg,#1a2a1a,#0f1f0f);border:1px solid rgba(52,211,153,0.3);padding:var(--s-3);display:flex;align-items:center;gap:var(--s-3);">
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#EF4444;animation:specPulse 1.5s infinite;"></span> <span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#EF4444;animation:specPulse 1.5s infinite;"></span>
<div style="flex:1;"> <div style="flex:1;">
<div style="font-size:13px;font-weight:600;color:#34D399;">يلعب الآن — ${gameLabel}</div> <div style="font-size:13px;font-weight:600;color:#34D399;">${t('profile.playing_now', { game: gameLabel })}</div>
</div> </div>
<button class="btn btn-primary" id="btn-spectate" style="min-height:32px;padding:4px 14px;font-size:12px;">${emoji('eye', '👁', 13)} شاهد</button> <button class="btn btn-primary" id="btn-spectate" style="min-height:32px;padding:4px 14px;font-size:12px;">${emoji('eye', '👁', 13)} ${t('profile.watch')}</button>
</div> </div>
<style>@keyframes specPulse{0%,100%{opacity:1}50%{opacity:0.3}}</style> <style>@keyframes specPulse{0%,100%{opacity:1}50%{opacity:0.3}}</style>
`; `;
...@@ -372,12 +372,12 @@ function renderActionButtons(friendStatus, blockStatus) { ...@@ -372,12 +372,12 @@ function renderActionButtons(friendStatus, blockStatus) {
let buttons = ''; let buttons = '';
if (friendStatus === 'accepted') { if (friendStatus === 'accepted') {
buttons += `<button class="btn btn-primary w-full" id="btn-challenge" style="min-height:44px;">${emoji('swords', '⚔️', 16)} تحدي</button>`; buttons += `<button class="btn btn-primary w-full" id="btn-challenge" style="min-height:44px;">${emoji('swords', '⚔️', 16)} ${t('profile.challenge')}</button>`;
buttons += `<button class="btn btn-secondary w-full" id="btn-message" style="min-height:44px;">${emoji('chat', '💬', 16)} مراسلة</button>`; buttons += `<button class="btn btn-secondary w-full" id="btn-message" style="min-height:44px;">${emoji('chat', '💬', 16)} ${t('profile.message')}</button>`;
} else if (friendStatus === 'pending') { } else if (friendStatus === 'pending') {
buttons += `<button class="btn btn-secondary w-full" disabled style="min-height:44px;opacity:0.6;">طلب صداقة معلق</button>`; buttons += `<button class="btn btn-secondary w-full" disabled style="min-height:44px;opacity:0.6;">${t('profile.pending_request')}</button>`;
} else { } else {
buttons += `<button class="btn btn-primary w-full" id="btn-add-friend" style="min-height:44px;">➕ إضافة صديق</button>`; buttons += `<button class="btn btn-primary w-full" id="btn-add-friend" style="min-height:44px;">${t('profile.add_friend')}</button>`;
} }
buttons += `<button class="btn btn-secondary w-full" id="btn-block" style="min-height:44px;color:var(--error);">${emoji('block', '🚫', 16)} ${t('block.block')}</button>`; buttons += `<button class="btn btn-secondary w-full" id="btn-block" style="min-height:44px;color:var(--error);">${emoji('block', '🚫', 16)} ${t('block.block')}</button>`;
...@@ -409,10 +409,10 @@ async function loadOrgMembership(el) { ...@@ -409,10 +409,10 @@ async function loadOrgMembership(el) {
<div style="display:flex;align-items:center;gap:var(--s-2);padding:var(--s-2) 0;"> <div style="display:flex;align-items:center;gap:var(--s-2);padding:var(--s-2) 0;">
${logoHtml} ${logoHtml}
<div style="flex:1;"> <div style="flex:1;">
<div style="font-size:13px;font-weight:600;color:var(--text-primary);">${org ? org.name : 'منظمة'}</div> <div style="font-size:13px;font-weight:600;color:var(--text-primary);">${org ? org.name : t('org.title')}</div>
<div style="font-size:11px;color:var(--text-secondary);">${m.role || 'عضو'}</div> <div style="font-size:11px;color:var(--text-secondary);">${m.role || t('profile.member')}</div>
</div> </div>
<span style="background:var(--success);color:#fff;font-size:10px;padding:2px 6px;border-radius:10px;">عضو</span> <span style="background:var(--success);color:#fff;font-size:10px;padding:2px 6px;border-radius:10px;">${t('profile.member')}</span>
</div> </div>
`; `;
}).join(''); }).join('');
...@@ -423,19 +423,19 @@ async function loadOrgMembership(el) { ...@@ -423,19 +423,19 @@ async function loadOrgMembership(el) {
if (pending.length > 0) { if (pending.length > 0) {
html += pending.map(a => { html += pending.map(a => {
const org = a.organizations || a.el3ab_organizations; const org = a.organizations || a.el3ab_organizations;
const orgName = org ? org.name : 'منظمة'; const orgName = org ? org.name : t('org.title');
const logoHtml = org && org.logo_url const logoHtml = org && org.logo_url
? `<img src="${org.logo_url}" style="width:28px;height:28px;border-radius:6px;object-fit:contain;" alt="">` ? `<img src="${org.logo_url}" style="width:28px;height:28px;border-radius:6px;object-fit:contain;" alt="">`
: `<div style="width:28px;height:28px;border-radius:6px;background:var(--bg-elevated);display:flex;align-items:center;justify-content:center;">${emoji('building', '🏢', 14)}</div>`; : `<div style="width:28px;height:28px;border-radius:6px;background:var(--bg-elevated);display:flex;align-items:center;justify-content:center;">${emoji('building', '🏢', 14)}</div>`;
const statusBadge = a.status === 'pending' const statusBadge = a.status === 'pending'
? `<span style="background:#f59e0b;color:#000;font-size:10px;padding:2px 6px;border-radius:10px;">قيد المراجعة</span>` ? `<span style="background:#f59e0b;color:#000;font-size:10px;padding:2px 6px;border-radius:10px;">${t('profile.pending_review')}</span>`
: `<span style="background:var(--error);color:#fff;font-size:10px;padding:2px 6px;border-radius:10px;">مرفوض</span>`; : `<span style="background:var(--error);color:#fff;font-size:10px;padding:2px 6px;border-radius:10px;">${t('profile.rejected')}</span>`;
return ` return `
<div style="display:flex;align-items:center;gap:var(--s-2);padding:var(--s-2) 0;"> <div style="display:flex;align-items:center;gap:var(--s-2);padding:var(--s-2) 0;">
${logoHtml} ${logoHtml}
<div style="flex:1;"> <div style="flex:1;">
<div style="font-size:13px;font-weight:600;color:var(--text-primary);">${orgName}</div> <div style="font-size:13px;font-weight:600;color:var(--text-primary);">${orgName}</div>
<div style="font-size:11px;color:var(--text-secondary);">طلب انضمام</div> <div style="font-size:11px;color:var(--text-secondary);">${t('profile.membership_request')}</div>
</div> </div>
${statusBadge} ${statusBadge}
</div> </div>
...@@ -443,9 +443,9 @@ async function loadOrgMembership(el) { ...@@ -443,9 +443,9 @@ async function loadOrgMembership(el) {
}).join(''); }).join('');
} }
content.innerHTML = html || `<div style="font-size:13px;color:var(--text-secondary);">لست عضواً في أي منظمة بعد</div>`; content.innerHTML = html || `<div style="font-size:13px;color:var(--text-secondary);">${t('profile.no_org')}</div>`;
} catch (e) { } catch (e) {
content.innerHTML = `<div style="font-size:13px;color:var(--text-secondary);">لست عضواً في أي منظمة بعد</div>`; content.innerHTML = `<div style="font-size:13px;color:var(--text-secondary);">${t('profile.no_org')}</div>`;
} }
} }
......
...@@ -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) });
} }
...@@ -4,6 +4,7 @@ import * as audio from '../../../core/audio.js'; ...@@ -4,6 +4,7 @@ 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 { emoji } from '../../../core/theme.js'; import { emoji } from '../../../core/theme.js';
import { t } from '../../../core/i18n.js';
let pollTimer = null; let pollTimer = null;
let friendId = null; let friendId = null;
...@@ -18,7 +19,7 @@ export function mountChat(el, params = {}) { ...@@ -18,7 +19,7 @@ export function mountChat(el, params = {}) {
isLoading = false; isLoading = false;
if (pollTimer) clearInterval(pollTimer); if (pollTimer) clearInterval(pollTimer);
const name = friendProfile?.display_name || friendProfile?.username || 'صديق'; const name = friendProfile?.display_name || friendProfile?.username || t('common.friend');
const avatar = friendProfile?.avatar_url; const avatar = friendProfile?.avatar_url;
el.innerHTML = ` el.innerHTML = `
...@@ -31,7 +32,7 @@ export function mountChat(el, params = {}) { ...@@ -31,7 +32,7 @@ export function mountChat(el, params = {}) {
</div> </div>
<div class="chat-header-info"> <div class="chat-header-info">
<div class="chat-header-name">${name}</div> <div class="chat-header-name">${name}</div>
<div class="chat-header-status" id="chat-status">${friendProfile?.is_online ? 'متصل الآن' : 'غير متصل'}</div> <div class="chat-header-status" id="chat-status">${friendProfile?.is_online ? t('common.online') : t('common.offline')}</div>
</div> </div>
<button id="chat-invite" class="chat-action-btn">${emoji('challenge_swords', '⚔️', 16)}</button> <button id="chat-invite" class="chat-action-btn">${emoji('challenge_swords', '⚔️', 16)}</button>
<button id="chat-profile" class="chat-action-btn">${emoji('person', '👤', 14)}</button> <button id="chat-profile" class="chat-action-btn">${emoji('person', '👤', 14)}</button>
...@@ -39,12 +40,12 @@ export function mountChat(el, params = {}) { ...@@ -39,12 +40,12 @@ export function mountChat(el, params = {}) {
<!-- Messages --> <!-- Messages -->
<div class="chat-messages" id="chat-messages"> <div class="chat-messages" id="chat-messages">
<div class="chat-loading" id="chat-loading">${emoji('loading', '⏳', 16)} جاري التحميل...</div> <div class="chat-loading" id="chat-loading">${emoji('loading', '⏳', 16)} ${t('common.loading')}</div>
</div> </div>
<!-- Input --> <!-- Input -->
<div class="chat-input-bar"> <div class="chat-input-bar">
<input type="text" id="chat-input" class="chat-input" placeholder="اكتب رسالة..." maxlength="500" autocomplete="off"> <input type="text" id="chat-input" class="chat-input" placeholder="${t('chat.placeholder')}" maxlength="500" autocomplete="off">
<button id="chat-send" class="chat-send-btn">${emoji('send', '📤', 18)}</button> <button id="chat-send" class="chat-send-btn">${emoji('send', '📤', 18)}</button>
</div> </div>
</div> </div>
...@@ -122,7 +123,7 @@ async function loadMessages(el) { ...@@ -122,7 +123,7 @@ async function loadMessages(el) {
renderMessages(el); renderMessages(el);
} catch (e) { } catch (e) {
const container = el.querySelector('#chat-messages'); const container = el.querySelector('#chat-messages');
container.innerHTML = '<div class="chat-loading" style="color:#ef4444;">فشل تحميل الرسائل</div>'; container.innerHTML = `<div class="chat-loading" style="color:#ef4444;">${t('chat.failed_load')}</div>`;
} }
} }
...@@ -200,7 +201,7 @@ function renderMessages(el, scrollToBottom = false) { ...@@ -200,7 +201,7 @@ function renderMessages(el, scrollToBottom = false) {
container.innerHTML = ` container.innerHTML = `
<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;opacity:0.6;"> <div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;opacity:0.6;">
<div style="font-size:32px;">${emoji('wave', '👋', 32)}</div> <div style="font-size:32px;">${emoji('wave', '👋', 32)}</div>
<div style="font-size:13px;color:#64748b;">ابدأ المحادثة مع صديقك!</div> <div style="font-size:13px;color:#64748b;">${t('chat.start_conversation')}</div>
</div> </div>
`; `;
return; return;
...@@ -223,10 +224,10 @@ function renderMessages(el, scrollToBottom = false) { ...@@ -223,10 +224,10 @@ function renderMessages(el, scrollToBottom = false) {
html += `<div class="chat-bubble system">${escapeHtml(msg.content)}</div>`; html += `<div class="chat-bubble system">${escapeHtml(msg.content)}</div>`;
} else if (msg.message_type === 'invite') { } else if (msg.message_type === 'invite') {
const meta = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata || '{}') : (msg.metadata || {}); const meta = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata || '{}') : (msg.metadata || {});
const gameLabel = meta.game_key === 'ludo' ? '🎲 لودو' : '♟ شطرنج'; const gameLabel = meta.game_key === 'ludo' ? '🎲 ' + t('game.ludo') : '♟ ' + t('game.chess');
html += ` html += `
<div class="chat-bubble ${isMine ? 'mine' : 'theirs'}" style="background:${isMine ? 'rgba(37,99,235,0.3)' : 'rgba(228,172,56,0.15)'};border:1px solid ${isMine ? 'rgba(37,99,235,0.3)' : 'rgba(228,172,56,0.3)'};"> <div class="chat-bubble ${isMine ? 'mine' : 'theirs'}" style="background:${isMine ? 'rgba(37,99,235,0.3)' : 'rgba(228,172,56,0.15)'};border:1px solid ${isMine ? 'rgba(37,99,235,0.3)' : 'rgba(228,172,56,0.3)'};">
<div style="font-size:12px;font-weight:600;margin-bottom:4px;">${emoji('challenge_swords', '⚔️', 13)} تحدي ${gameLabel}</div> <div style="font-size:12px;font-weight:600;margin-bottom:4px;">${emoji('challenge_swords', '⚔️', 13)} ${t('chat.challenge_title', { name: gameLabel })}</div>
<div style="font-size:12px;opacity:0.8;">${escapeHtml(msg.content)}</div> <div style="font-size:12px;opacity:0.8;">${escapeHtml(msg.content)}</div>
<div class="chat-time">${time}</div> <div class="chat-time">${time}</div>
</div> </div>
...@@ -252,7 +253,7 @@ function showInviteFromChat(el) { ...@@ -252,7 +253,7 @@ function showInviteFromChat(el) {
const existing = document.getElementById('chat-invite-dialog'); const existing = document.getElementById('chat-invite-dialog');
if (existing) { existing.remove(); return; } if (existing) { existing.remove(); return; }
const name = friendProfile?.display_name || friendProfile?.username || 'صديق'; const name = friendProfile?.display_name || friendProfile?.username || t('common.friend');
const dialog = document.createElement('div'); const dialog = document.createElement('div');
dialog.id = 'chat-invite-dialog'; dialog.id = 'chat-invite-dialog';
...@@ -260,24 +261,24 @@ function showInviteFromChat(el) { ...@@ -260,24 +261,24 @@ function showInviteFromChat(el) {
dialog.innerHTML = ` dialog.innerHTML = `
<div style="background:#1a1a2e;border-radius:16px;padding:24px;width:100%;max-width:300px;text-align:center;"> <div style="background:#1a1a2e;border-radius:16px;padding:24px;width:100%;max-width:300px;text-align:center;">
<div style="font-size:24px;margin-bottom:8px;">${emoji('challenge_swords', '⚔️', 24)}</div> <div style="font-size:24px;margin-bottom:8px;">${emoji('challenge_swords', '⚔️', 24)}</div>
<div style="font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:4px;">تحدّي ${name}</div> <div style="font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:4px;">${t('chat.challenge_title', { name })}</div>
<div style="font-size:12px;color:#64748b;margin-bottom:16px;">اختر اللعبة</div> <div style="font-size:12px;color:#64748b;margin-bottom:16px;">${t('chat.choose_game')}</div>
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:12px;"> <div style="display:flex;gap:8px;justify-content:center;margin-bottom:12px;">
<button class="cig active" data-game="chess" style="flex:1;padding:10px;border-radius:10px;background:#2563EB;border:2px solid #2563EB;color:#fff;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">♟ شطرنج</button> <button class="cig active" data-game="chess" style="flex:1;padding:10px;border-radius:10px;background:#2563EB;border:2px solid #2563EB;color:#fff;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">♟ ${t('game.chess')}</button>
<button class="cig" data-game="domino" style="flex:1;padding:10px;border-radius:10px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">🁣 دومينو</button> <button class="cig" data-game="domino" style="flex:1;padding:10px;border-radius:10px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">🁣 ${t('game.domino')}</button>
<button class="cig" data-game="ludo" style="flex:1;padding:10px;border-radius:10px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">🎲 لودو</button> <button class="cig" data-game="ludo" style="flex:1;padding:10px;border-radius:10px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">🎲 ${t('game.ludo')}</button>
</div> </div>
<div id="cig-time" style="display:flex;gap:6px;justify-content:center;margin-bottom:16px;flex-wrap:wrap;"> <div id="cig-time" style="display:flex;gap:6px;justify-content:center;margin-bottom:16px;flex-wrap:wrap;">
<button class="cit" data-tc="bullet_1_0" style="padding:6px 10px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">1 د</button> <button class="cit" data-tc="bullet_1_0" style="padding:6px 10px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">${t('time.1min')}</button>
<button class="cit active" data-tc="blitz_3_0" style="padding:6px 10px;border-radius:8px;background:#E4AC38;border:none;color:#1a1a1a;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">3 د</button> <button class="cit active" data-tc="blitz_3_0" style="padding:6px 10px;border-radius:8px;background:#E4AC38;border:none;color:#1a1a1a;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">${t('time.3min')}</button>
<button class="cit" data-tc="blitz_5_0" style="padding:6px 10px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">5 د</button> <button class="cit" data-tc="blitz_5_0" style="padding:6px 10px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">${t('time.5min')}</button>
<button class="cit" data-tc="rapid_10_0" style="padding:6px 10px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">10 د</button> <button class="cit" data-tc="rapid_10_0" style="padding:6px 10px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">${t('time.10min')}</button>
</div> </div>
<button class="btn btn-primary" id="cig-send" style="width:100%;font-size:14px;padding:12px;">أرسل التحدي</button> <button class="btn btn-primary" id="cig-send" style="width:100%;font-size:14px;padding:12px;">${t('challenge.send')}</button>
<button id="cig-cancel" style="margin-top:8px;background:none;border:none;color:#64748b;font-size:12px;cursor:pointer;font-family:inherit;">إلغاء</button> <button id="cig-cancel" style="margin-top:8px;background:none;border:none;color:#64748b;font-size:12px;cursor:pointer;font-family:inherit;">${t('common.cancel')}</button>
</div> </div>
`; `;
...@@ -306,7 +307,7 @@ function showInviteFromChat(el) { ...@@ -306,7 +307,7 @@ function showInviteFromChat(el) {
dialog.querySelector('#cig-send').addEventListener('click', async () => { dialog.querySelector('#cig-send').addEventListener('click', async () => {
const sendBtn = dialog.querySelector('#cig-send'); const sendBtn = dialog.querySelector('#cig-send');
sendBtn.disabled = true; sendBtn.disabled = true;
sendBtn.textContent = 'جاري الإرسال...'; sendBtn.textContent = t('common.sending');
try { try {
const res = await net.post('friends.php', { const res = await net.post('friends.php', {
...@@ -322,7 +323,7 @@ function showInviteFromChat(el) { ...@@ -322,7 +323,7 @@ function showInviteFromChat(el) {
await net.post('chat.php', { await net.post('chat.php', {
action: 'send', action: 'send',
friend_id: friendId, friend_id: friendId,
content: `أرسل تحدي ${selectedGame === 'ludo' ? 'لودو' : selectedGame === 'domino' ? 'دومينو' : 'شطرنج'}`, content: t('challenge.sent_msg', { game: selectedGame === 'ludo' ? t('game.ludo') : selectedGame === 'domino' ? t('game.domino') : t('game.chess') }),
message_type: 'invite', message_type: 'invite',
metadata: { game_key: selectedGame, time_control: selectedTc, match_id: res.match_id } metadata: { game_key: selectedGame, time_control: selectedTc, match_id: res.match_id }
}); });
...@@ -342,7 +343,7 @@ function showInviteFromChat(el) { ...@@ -342,7 +343,7 @@ function showInviteFromChat(el) {
isHost: true isHost: true
}); });
} catch (e) { } catch (e) {
sendBtn.textContent = 'فشل — حاول مرة أخرى'; sendBtn.textContent = t('challenge.failed_retry');
sendBtn.disabled = false; sendBtn.disabled = false;
} }
}); });
......
...@@ -22,14 +22,14 @@ export function mountFriends(el) { ...@@ -22,14 +22,14 @@ export function mountFriends(el) {
<div style="padding:12px 16px;background:#0f0f1e;"> <div style="padding:12px 16px;background:#0f0f1e;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;"> <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
<h2 style="font-size:18px;font-weight:700;color:#f8fafc;">${t('social.friends')}</h2> <h2 style="font-size:18px;font-weight:700;color:#f8fafc;">${t('social.friends')}</h2>
<button class="btn btn-secondary" id="btn-search" style="min-height:34px;padding:6px 14px;font-size:12px;">${emoji('search_icon', '🔍', 13)} بحث</button> <button class="btn btn-secondary" id="btn-search" style="min-height:34px;padding:6px 14px;font-size:12px;">${emoji('search_icon', '🔍', 13)} ${t('social.search_btn')}</button>
</div> </div>
<div style="display:flex;gap:6px;flex-wrap:wrap;"> <div style="display:flex;gap:6px;flex-wrap:wrap;">
<button class="social-tab active" data-tab="friends">الأصدقاء</button> <button class="social-tab active" data-tab="friends">${t('social.friends_tab')}</button>
<button class="social-tab" data-tab="pending">الطلبات <span id="pending-count" style="display:none;font-size:10px;background:#EF4444;color:#fff;border-radius:50%;padding:1px 5px;margin-right:2px;"></span></button> <button class="social-tab" data-tab="pending">${t('social.pending_tab')} <span id="pending-count" style="display:none;font-size:10px;background:#EF4444;color:#fff;border-radius:50%;padding:1px 5px;margin-right:2px;"></span></button>
<button class="social-tab" data-tab="online">متصلين</button> <button class="social-tab" data-tab="online">${t('social.online_tab')}</button>
<button class="social-tab" data-tab="groups">${emoji('group', '👥', 12)} المجموعات</button> <button class="social-tab" data-tab="groups">${emoji('group', '👥', 12)} ${t('social.groups_tab')}</button>
<button class="social-tab" data-tab="activity">${emoji('news', '📰', 12)} أخبار</button> <button class="social-tab" data-tab="activity">${emoji('news', '📰', 12)} ${t('social.activity_tab')}</button>
</div> </div>
</div> </div>
<!-- Invite banner --> <!-- Invite banner -->
...@@ -102,16 +102,16 @@ async function checkInvites(el) { ...@@ -102,16 +102,16 @@ async function checkInvites(el) {
banner.style.display = 'block'; banner.style.display = 'block';
banner.innerHTML = invites.map(inv => { banner.innerHTML = invites.map(inv => {
const from = profiles[inv.from_id] || {}; const from = profiles[inv.from_id] || {};
const name = from.display_name || from.username || 'صديق'; const name = from.display_name || from.username || t('common.friend');
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');
return ` return `
<div style="display:flex;align-items:center;gap:10px;padding:10px 16px;background:linear-gradient(135deg,#1a2a1a,#0f1f0f);border-bottom:1px solid rgba(52,211,153,0.2);animation:slideDown 0.3s;"> <div style="display:flex;align-items:center;gap:10px;padding:10px 16px;background:linear-gradient(135deg,#1a2a1a,#0f1f0f);border-bottom:1px solid rgba(52,211,153,0.2);animation:slideDown 0.3s;">
<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:600;color:#34D399;">${name} يتحداك!</div> <div style="font-size:13px;font-weight:600;color:#34D399;">${t('social.challenges_you', { name })}</div>
<div style="font-size:11px;color:#64748b;">${gameLabel}</div> <div style="font-size:11px;color:#64748b;">${gameLabel}</div>
</div> </div>
<button class="btn btn-primary" data-accept-invite="${inv.match_id}" style="min-height:30px;padding:5px 14px;font-size:11px;">قبول</button> <button class="btn btn-primary" data-accept-invite="${inv.match_id}" style="min-height:30px;padding:5px 14px;font-size:11px;">${t('social.accept')}</button>
<button class="btn btn-secondary" data-decline-invite="${inv.match_id}" style="min-height:30px;padding:5px 10px;font-size:11px;">✕</button> <button class="btn btn-secondary" data-decline-invite="${inv.match_id}" style="min-height:30px;padding:5px 10px;font-size:11px;">✕</button>
</div> </div>
`; `;
...@@ -124,7 +124,7 @@ async function checkInvites(el) { ...@@ -124,7 +124,7 @@ async function checkInvites(el) {
try { try {
const inv = invites.find(i => i.match_id === btn.dataset.acceptInvite); const inv = invites.find(i => i.match_id === btn.dataset.acceptInvite);
const res = await net.post('friends.php', { action: 'accept-invite', match_id: btn.dataset.acceptInvite, game_key: inv?.game_key || 'chess' }); const res = await net.post('friends.php', { action: 'accept-invite', match_id: btn.dataset.acceptInvite, game_key: inv?.game_key || 'chess' });
if (res.error) { btn.textContent = 'خطأ'; return; } if (res.error) { btn.textContent = t('social.error'); return; }
audio.play('reward'); audio.play('reward');
juice.hapticSuccess(); juice.hapticSuccess();
scene.push('game-lobby', { scene.push('game-lobby', {
...@@ -137,7 +137,7 @@ async function checkInvites(el) { ...@@ -137,7 +137,7 @@ async function checkInvites(el) {
isHost: false isHost: false
}); });
} catch (e) { } catch (e) {
btn.textContent = 'فشل'; btn.textContent = t('common.failed');
} }
}); });
}); });
...@@ -173,7 +173,7 @@ async function loadPendingCount(el) { ...@@ -173,7 +173,7 @@ async function loadPendingCount(el) {
async function loadTab(el, tab) { async function loadTab(el, tab) {
const content = el.querySelector('#social-content'); const content = el.querySelector('#social-content');
content.innerHTML = '<div style="text-align:center;padding:20px;color:#64748b;">جاري التحميل...</div>'; content.innerHTML = `<div style="text-align:center;padding:20px;color:#64748b;">${t('common.loading')}</div>`;
switch (tab) { switch (tab) {
case 'friends': await loadFriends(content); break; case 'friends': await loadFriends(content); break;
...@@ -193,9 +193,9 @@ async function loadFriends(content) { ...@@ -193,9 +193,9 @@ async function loadFriends(content) {
content.innerHTML = ` content.innerHTML = `
<div style="text-align:center;padding:40px 20px;"> <div style="text-align:center;padding:40px 20px;">
<div style="font-size:48px;margin-bottom:12px;opacity:0.5;">${emoji('people', '👥', 48)}</div> <div style="font-size:48px;margin-bottom:12px;opacity:0.5;">${emoji('people', '👥', 48)}</div>
<div style="font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:6px;">لا يوجد أصدقاء بعد</div> <div style="font-size:15px;font-weight:700;color:#f8fafc;margin-bottom:6px;">${t('social.no_friends')}</div>
<div style="font-size:12px;color:#64748b;margin-bottom:16px;">ابحث عن لاعبين وأرسل لهم طلب صداقة</div> <div style="font-size:12px;color:#64748b;margin-bottom:16px;">${t('social.no_friends_hint')}</div>
<button class="btn btn-primary" id="empty-search" style="font-size:13px;padding:10px 24px;">${emoji('search_icon', '🔍', 13)} ابحث عن لاعبين</button> <button class="btn btn-primary" id="empty-search" style="font-size:13px;padding:10px 24px;">${emoji('search_icon', '🔍', 13)} ${t('social.search_players')}</button>
</div>`; </div>`;
content.querySelector('#empty-search')?.addEventListener('click', () => { content.querySelector('#empty-search')?.addEventListener('click', () => {
showSearch(content.closest('[style*="height:100%"]') || content.parentElement); showSearch(content.closest('[style*="height:100%"]') || content.parentElement);
...@@ -208,12 +208,12 @@ async function loadFriends(content) { ...@@ -208,12 +208,12 @@ async function loadFriends(content) {
const sorted = [...online, ...offline]; const sorted = [...online, ...offline];
content.innerHTML = ` content.innerHTML = `
<div style="font-size:11px;color:#64748b;margin-bottom:8px;">${friends.length} أصدقاء${online.length > 0 ? ` — ${online.length} متصل` : ''}</div> <div style="font-size:11px;color:#64748b;margin-bottom:8px;">${t('social.friends_count', { n: friends.length })}${online.length > 0 ? ` — ${t('social.online_count', { n: online.length })}` : ''}</div>
${sorted.map(f => renderFriendCard(f)).join('')} ${sorted.map(f => renderFriendCard(f)).join('')}
`; `;
bindFriendActions(content); bindFriendActions(content);
} catch (e) { } catch (e) {
content.innerHTML = `<div style="text-align:center;color:#ef4444;padding:24px;">فشل التحميل — <span style="text-decoration:underline;cursor:pointer;" id="retry-friends">حاول مرة أخرى</span></div>`; content.innerHTML = `<div style="text-align:center;color:#ef4444;padding:24px;">${t('common.error_load')} — <span style="text-decoration:underline;cursor:pointer;" id="retry-friends">${t('social.retry_load')}</span></div>`;
content.querySelector('#retry-friends')?.addEventListener('click', () => loadFriends(content)); content.querySelector('#retry-friends')?.addEventListener('click', () => loadFriends(content));
} }
} }
...@@ -227,7 +227,7 @@ async function loadPending(content, rootEl) { ...@@ -227,7 +227,7 @@ async function loadPending(content, rootEl) {
content.innerHTML = ` content.innerHTML = `
<div style="text-align:center;padding:40px;color:#64748b;"> <div style="text-align:center;padding:40px;color:#64748b;">
<div style="font-size:32px;margin-bottom:8px;opacity:0.5;">${emoji('inbox', '📥', 32)}</div> <div style="font-size:32px;margin-bottom:8px;opacity:0.5;">${emoji('inbox', '📥', 32)}</div>
<div>لا توجد طلبات معلقة</div> <div>${t('social.no_pending')}</div>
</div>`; </div>`;
return; return;
} }
...@@ -242,10 +242,10 @@ async function loadPending(content, rootEl) { ...@@ -242,10 +242,10 @@ async function loadPending(content, rootEl) {
} }
content.innerHTML = ` content.innerHTML = `
<div style="font-size:11px;color:#64748b;margin-bottom:10px;">${pending.length} طلب معلق</div> <div style="font-size:11px;color:#64748b;margin-bottom:10px;">${t('social.pending_count', { n: pending.length })}</div>
${pending.map(p => { ${pending.map(p => {
const profile = profiles[p.requester_id] || {}; const profile = profiles[p.requester_id] || {};
const name = profile.display_name || profile.username || p.requester_id?.substring(0, 8) || 'لاعب'; const name = profile.display_name || profile.username || p.requester_id?.substring(0, 8) || t('common.player');
const avatar = profile.avatar_url; const avatar = profile.avatar_url;
const level = profile.level || 1; const level = profile.level || 1;
return ` return `
...@@ -255,11 +255,11 @@ async function loadPending(content, rootEl) { ...@@ -255,11 +255,11 @@ async function loadPending(content, rootEl) {
</div> </div>
<div style="flex:1;min-width:0;"> <div style="flex:1;min-width:0;">
<div style="font-size:13px;font-weight:600;color:#f8fafc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${name}</div> <div style="font-size:13px;font-weight:600;color:#f8fafc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${name}</div>
<div style="font-size:11px;color:#64748b;">مستوى ${level} — أرسل لك طلب صداقة</div> <div style="font-size:11px;color:#64748b;">${t('common.level', { n: level })}${t('social.sent_friend_request')}</div>
</div> </div>
<div class="friend-actions"> <div class="friend-actions">
<div class="friend-action" data-accept="${p.id}" title="قبول" style="background:rgba(52,211,153,0.15);border-color:rgba(52,211,153,0.3);color:#34D399;font-weight:700;">✓</div> <div class="friend-action" data-accept="${p.id}" title="${t('social.accept')}" style="background:rgba(52,211,153,0.15);border-color:rgba(52,211,153,0.3);color:#34D399;font-weight:700;">✓</div>
<div class="friend-action" data-reject="${p.id}" title="رفض" style="background:rgba(239,68,68,0.1);border-color:rgba(239,68,68,0.2);color:#EF4444;">✕</div> <div class="friend-action" data-reject="${p.id}" title="${t('common.cancel')}" style="background:rgba(239,68,68,0.1);border-color:rgba(239,68,68,0.2);color:#EF4444;">✕</div>
</div> </div>
</div> </div>
`; `;
...@@ -281,7 +281,7 @@ async function loadPending(content, rootEl) { ...@@ -281,7 +281,7 @@ async function loadPending(content, rootEl) {
} }
audio.play('reward'); audio.play('reward');
juice.hapticLight(); juice.hapticLight();
card.querySelector('.friend-actions').innerHTML = '<span style="font-size:11px;color:#34D399;font-weight:600;">✓ تم القبول</span>'; card.querySelector('.friend-actions').innerHTML = `<span style="font-size:11px;color:#34D399;font-weight:600;">${t('common.accepted')}</span>`;
card.style.borderRight = '3px solid #34D399'; card.style.borderRight = '3px solid #34D399';
loadPendingCount(rootEl); loadPendingCount(rootEl);
} catch (e) { } catch (e) {
...@@ -307,7 +307,7 @@ async function loadPending(content, rootEl) { ...@@ -307,7 +307,7 @@ async function loadPending(content, rootEl) {
}); });
}); });
} catch (e) { } catch (e) {
content.innerHTML = `<div style="text-align:center;color:#ef4444;">فشل التحميل</div>`; content.innerHTML = `<div style="text-align:center;color:#ef4444;">${t('common.error_load')}</div>`;
} }
} }
...@@ -320,19 +320,19 @@ async function loadOnline(content) { ...@@ -320,19 +320,19 @@ async function loadOnline(content) {
content.innerHTML = ` content.innerHTML = `
<div style="text-align:center;padding:40px;color:#64748b;"> <div style="text-align:center;padding:40px;color:#64748b;">
<div style="font-size:32px;margin-bottom:8px;opacity:0.5;">${emoji('sleeping', '😴', 32)}</div> <div style="font-size:32px;margin-bottom:8px;opacity:0.5;">${emoji('sleeping', '😴', 32)}</div>
<div style="font-size:14px;margin-bottom:4px;">لا يوجد أصدقاء متصلين الآن</div> <div style="font-size:14px;margin-bottom:4px;">${t('social.no_online_friends')}</div>
<div style="font-size:11px;color:#475569;">سيظهرون هنا عند دخولهم</div> <div style="font-size:11px;color:#475569;">${t('social.will_appear_online')}</div>
</div>`; </div>`;
return; return;
} }
content.innerHTML = ` content.innerHTML = `
<div style="font-size:11px;color:#34D399;margin-bottom:10px;">${friends.length} متصل الآن</div> <div style="font-size:11px;color:#34D399;margin-bottom:10px;">${t('social.online_now_count', { n: friends.length })}</div>
${friends.map(f => renderFriendCard(f)).join('')} ${friends.map(f => renderFriendCard(f)).join('')}
`; `;
bindFriendActions(content); bindFriendActions(content);
} catch (e) { } catch (e) {
content.innerHTML = `<div style="text-align:center;color:#ef4444;">فشل التحميل</div>`; content.innerHTML = `<div style="text-align:center;color:#ef4444;">${t('common.error_load')}</div>`;
} }
} }
...@@ -344,13 +344,13 @@ function renderFriendCard(f) { ...@@ -344,13 +344,13 @@ function renderFriendCard(f) {
${f.is_online ? '<div class="online-dot"></div>' : ''} ${f.is_online ? '<div class="online-dot"></div>' : ''}
</div> </div>
<div style="flex:1;min-width:0;"> <div style="flex:1;min-width:0;">
<div style="font-size:13px;font-weight:600;color:#f8fafc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${f.display_name || f.username || 'لاعب'}</div> <div style="font-size:13px;font-weight:600;color:#f8fafc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${f.display_name || f.username || t('common.player')}</div>
<div style="font-size:11px;color:${f.is_online ? '#34D399' : '#64748b'};">${f.is_online ? 'متصل الآن' : 'غير متصل'}${f.level ? ` — مستوى ${f.level}` : ''}</div> <div style="font-size:11px;color:${f.is_online ? '#34D399' : '#64748b'};">${f.is_online ? t('common.online') : t('common.offline')}${f.level ? ` — ${t('common.level', { n: f.level })}` : ''}</div>
</div> </div>
<div class="friend-actions"> <div class="friend-actions">
<div class="friend-action" data-chat="${f.id}" title="محادثة" style="background:rgba(37,99,235,0.15);border-color:rgba(37,99,235,0.3);color:#3B82F6;">💬</div> <div class="friend-action" data-chat="${f.id}" title="${t('social.chat')}" style="background:rgba(37,99,235,0.15);border-color:rgba(37,99,235,0.3);color:#3B82F6;">💬</div>
${f.is_online ? `<div class="friend-action" data-invite="${f.id}" title="تحدّي" style="background:rgba(228,172,56,0.15);border-color:rgba(228,172,56,0.3);color:#E4AC38;">${emoji('challenge_swords', '⚔️', 14)}</div>` : ''} ${f.is_online ? `<div class="friend-action" data-invite="${f.id}" title="${t('challenge.send')}" style="background:rgba(228,172,56,0.15);border-color:rgba(228,172,56,0.3);color:#E4AC38;">${emoji('challenge_swords', '⚔️', 14)}</div>` : ''}
<div class="friend-action" data-remove="${f.id}" title="إزالة" style="font-size:11px;color:#64748b;">✕</div> <div class="friend-action" data-remove="${f.id}" title="${t('social.remove_friend')}" style="font-size:11px;color:#64748b;">✕</div>
</div> </div>
</div> </div>
`; `;
...@@ -375,7 +375,7 @@ function bindFriendActions(content) { ...@@ -375,7 +375,7 @@ function bindFriendActions(content) {
juice.hapticLight(); juice.hapticLight();
const uid = btn.dataset.invite; const uid = btn.dataset.invite;
const card = btn.closest('.friend-card'); const card = btn.closest('.friend-card');
const name = card?.querySelector('[style*="font-weight:600"]')?.textContent || 'صديق'; const name = card?.querySelector('[style*="font-weight:600"]')?.textContent || t('common.friend');
showInviteDialog(content, uid, name); showInviteDialog(content, uid, name);
}); });
}); });
...@@ -384,12 +384,12 @@ function bindFriendActions(content) { ...@@ -384,12 +384,12 @@ function bindFriendActions(content) {
btn.addEventListener('click', async () => { btn.addEventListener('click', async () => {
audio.play('click'); audio.play('click');
const card = btn.closest('.friend-card'); const card = btn.closest('.friend-card');
const name = card?.querySelector('[style*="font-weight:600"]')?.textContent || 'هذا الصديق'; const name = card?.querySelector('[style*="font-weight:600"]')?.textContent || t('common.friend');
const confirmed = await modal.confirm(`إزالة ${name} من الأصدقاء؟`, { const confirmed = await modal.confirm(t('social.remove_confirm', { name }), {
title: 'إزالة صديق', title: t('social.remove_friend'),
icon: '👋', icon: '👋',
confirmText: 'إزالة', confirmText: t('social.remove_friend'),
cancelText: 'إلغاء', cancelText: t('common.cancel'),
danger: true danger: true
}); });
if (!confirmed) return; if (!confirmed) return;
...@@ -414,23 +414,23 @@ function showInviteDialog(content, targetId, targetName) { ...@@ -414,23 +414,23 @@ function showInviteDialog(content, targetId, targetName) {
dialog.innerHTML = ` dialog.innerHTML = `
<div style="background:#1a1a2e;border-radius:16px;padding:24px;width:100%;max-width:320px;text-align:center;"> <div style="background:#1a1a2e;border-radius:16px;padding:24px;width:100%;max-width:320px;text-align:center;">
<div style="font-size:24px;margin-bottom:8px;">⚔️</div> <div style="font-size:24px;margin-bottom:8px;">⚔️</div>
<div style="font-size:16px;font-weight:700;color:#f8fafc;margin-bottom:4px;">تحدّي ${targetName}</div> <div style="font-size:16px;font-weight:700;color:#f8fafc;margin-bottom:4px;">${t('challenge.send')} ${targetName}</div>
<div style="font-size:12px;color:#64748b;margin-bottom:16px;">اختر اللعبة ونوع الوقت</div> <div style="font-size:12px;color:#64748b;margin-bottom:16px;">${t('challenge.select_game')}</div>
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:12px;"> <div style="display:flex;gap:8px;justify-content:center;margin-bottom:12px;">
<button class="inv-game active" data-game="chess" style="flex:1;padding:10px;border-radius:10px;background:#2563EB;border:2px solid #2563EB;color:#fff;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">♟ شطرنج</button> <button class="inv-game active" data-game="chess" style="flex:1;padding:10px;border-radius:10px;background:#2563EB;border:2px solid #2563EB;color:#fff;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">♟ ${t('game.chess')}</button>
<button class="inv-game" data-game="ludo" style="flex:1;padding:10px;border-radius:10px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">🎲 لودو</button> <button class="inv-game" data-game="ludo" style="flex:1;padding:10px;border-radius:10px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">🎲 ${t('game.ludo')}</button>
</div> </div>
<div id="time-options" style="display:flex;gap:6px;justify-content:center;margin-bottom:16px;flex-wrap:wrap;"> <div id="time-options" style="display:flex;gap:6px;justify-content:center;margin-bottom:16px;flex-wrap:wrap;">
<button class="inv-time active" data-tc="bullet_1_0" style="padding:6px 12px;border-radius:8px;background:#E4AC38;border:none;color:#1a1a1a;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">1 دقيقة</button> <button class="inv-time active" data-tc="bullet_1_0" style="padding:6px 12px;border-radius:8px;background:#E4AC38;border:none;color:#1a1a1a;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">${t('time.1min')}</button>
<button class="inv-time" data-tc="blitz_3_0" style="padding:6px 12px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">3 دقائق</button> <button class="inv-time" data-tc="blitz_3_0" style="padding:6px 12px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">${t('time.3min')}</button>
<button class="inv-time" data-tc="blitz_5_0" style="padding:6px 12px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">5 دقائق</button> <button class="inv-time" data-tc="blitz_5_0" style="padding:6px 12px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">${t('time.5min')}</button>
<button class="inv-time" data-tc="rapid_10_0" style="padding:6px 12px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">10 دقائق</button> <button class="inv-time" data-tc="rapid_10_0" style="padding:6px 12px;border-radius:8px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:11px;font-weight:600;cursor:pointer;font-family:inherit;">${t('time.10min')}</button>
</div> </div>
<button class="btn btn-primary" id="send-invite" style="width:100%;font-size:14px;padding:12px;">أرسل التحدي</button> <button class="btn btn-primary" id="send-invite" style="width:100%;font-size:14px;padding:12px;">${t('challenge.send')}</button>
<button id="cancel-invite" style="margin-top:8px;background:none;border:none;color:#64748b;font-size:12px;cursor:pointer;font-family:inherit;">إلغاء</button> <button id="cancel-invite" style="margin-top:8px;background:none;border:none;color:#64748b;font-size:12px;cursor:pointer;font-family:inherit;">${t('common.cancel')}</button>
</div> </div>
`; `;
...@@ -477,7 +477,7 @@ function showInviteDialog(content, targetId, targetName) { ...@@ -477,7 +477,7 @@ function showInviteDialog(content, targetId, targetName) {
dialog.querySelector('#send-invite').addEventListener('click', async () => { dialog.querySelector('#send-invite').addEventListener('click', async () => {
const sendBtn = dialog.querySelector('#send-invite'); const sendBtn = dialog.querySelector('#send-invite');
sendBtn.disabled = true; sendBtn.disabled = true;
sendBtn.textContent = 'جاري الإرسال...'; sendBtn.textContent = t('common.sending');
try { try {
const res = await net.post('friends.php', { const res = await net.post('friends.php', {
...@@ -495,7 +495,7 @@ function showInviteDialog(content, targetId, targetName) { ...@@ -495,7 +495,7 @@ function showInviteDialog(content, targetId, targetName) {
audio.play('reward'); audio.play('reward');
juice.hapticSuccess(); juice.hapticSuccess();
sendBtn.textContent = '✓ تم إرسال التحدي!'; sendBtn.textContent = t('challenge.sent');
sendBtn.style.background = '#34D399'; sendBtn.style.background = '#34D399';
// Navigate to lobby // Navigate to lobby
...@@ -511,7 +511,7 @@ function showInviteDialog(content, targetId, targetName) { ...@@ -511,7 +511,7 @@ function showInviteDialog(content, targetId, targetName) {
}); });
}, 800); }, 800);
} catch (e) { } catch (e) {
sendBtn.textContent = 'فشل — حاول مرة أخرى'; sendBtn.textContent = t('challenge.failed_retry');
sendBtn.disabled = false; sendBtn.disabled = false;
} }
}); });
...@@ -525,10 +525,10 @@ function showSearch(el) { ...@@ -525,10 +525,10 @@ function showSearch(el) {
content.innerHTML = ` content.innerHTML = `
<div style="margin-bottom:16px;"> <div style="margin-bottom:16px;">
<div style="display:flex;gap:8px;"> <div style="display:flex;gap:8px;">
<input class="input" id="search-input" type="text" placeholder="اسم المستخدم أو الاسم..." style="flex:1;min-height:40px;font-size:14px;" autocomplete="off"> <input class="input" id="search-input" type="text" placeholder="${t('social.search_placeholder')}" style="flex:1;min-height:40px;font-size:14px;" autocomplete="off">
<button class="btn btn-primary" id="search-go" style="min-height:40px;padding:8px 16px;font-size:13px;">بحث</button> <button class="btn btn-primary" id="search-go" style="min-height:40px;padding:8px 16px;font-size:13px;">${t('social.search_btn')}</button>
</div> </div>
<div style="font-size:11px;color:#475569;margin-top:4px;">ابحث بأي جزء من الاسم (حرفين على الأقل)</div> <div style="font-size:11px;color:#475569;margin-top:4px;">${t('social.search_hint')}</div>
</div> </div>
<div id="search-results"></div> <div id="search-results"></div>
`; `;
...@@ -541,17 +541,17 @@ function showSearch(el) { ...@@ -541,17 +541,17 @@ function showSearch(el) {
const doSearch = async () => { const doSearch = async () => {
const query = input.value.trim(); const query = input.value.trim();
if (query.length < 2) { if (query.length < 2) {
content.querySelector('#search-results').innerHTML = '<div style="text-align:center;color:#64748b;padding:12px;font-size:12px;">أدخل حرفين على الأقل</div>'; content.querySelector('#search-results').innerHTML = `<div style="text-align:center;color:#64748b;padding:12px;font-size:12px;">${t('social.search_min_chars')}</div>`;
return; return;
} }
const results = content.querySelector('#search-results'); const results = content.querySelector('#search-results');
results.innerHTML = '<div style="text-align:center;color:#64748b;padding:12px;">جاري البحث...</div>'; results.innerHTML = `<div style="text-align:center;color:#64748b;padding:12px;">${t('social.searching')}</div>`;
try { try {
const data = await net.get('friends.php', { action: 'search', query }); const data = await net.get('friends.php', { action: 'search', query });
const players = data.players || []; const players = data.players || [];
if (players.length === 0) { if (players.length === 0) {
results.innerHTML = '<div style="text-align:center;color:#64748b;padding:24px;font-size:13px;">لم يتم العثور على نتائج — جرب اسم آخر</div>'; results.innerHTML = `<div style="text-align:center;color:#64748b;padding:24px;font-size:13px;">${t('social.no_results')}</div>`;
return; return;
} }
results.innerHTML = players.map(p => ` results.innerHTML = players.map(p => `
...@@ -561,10 +561,10 @@ function showSearch(el) { ...@@ -561,10 +561,10 @@ function showSearch(el) {
${p.is_online ? '<div class="online-dot"></div>' : ''} ${p.is_online ? '<div class="online-dot"></div>' : ''}
</div> </div>
<div style="flex:1;min-width:0;"> <div style="flex:1;min-width:0;">
<div style="font-size:13px;font-weight:600;color:#f8fafc;">${p.display_name || p.username || 'لاعب'}</div> <div style="font-size:13px;font-weight:600;color:#f8fafc;">${p.display_name || p.username || t('common.player')}</div>
<div style="font-size:11px;color:#64748b;">${p.is_online ? 'متصل' : 'غير متصل'} — مستوى ${p.level || 1}</div> <div style="font-size:11px;color:#64748b;">${p.is_online ? t('common.online') : t('common.offline')}${t('common.level', { n: p.level || 1 })}</div>
</div> </div>
<button class="btn btn-primary" data-add="${p.id}" style="min-height:32px;padding:6px 12px;font-size:11px;flex-shrink:0;">+ أضف</button> <button class="btn btn-primary" data-add="${p.id}" style="min-height:32px;padding:6px 12px;font-size:11px;flex-shrink:0;">${t('social.add_btn')}</button>
</div> </div>
`).join(''); `).join('');
...@@ -575,30 +575,30 @@ function showSearch(el) { ...@@ -575,30 +575,30 @@ function showSearch(el) {
try { try {
const res = await net.post('friends.php', { action: 'request', target_id: addBtn.dataset.add }); const res = await net.post('friends.php', { action: 'request', target_id: addBtn.dataset.add });
if (res.error) { if (res.error) {
addBtn.textContent = res.error === 'Already friends' ? 'صديق بالفعل' : res.error === 'Request already pending' ? 'مرسل سابقاً' : res.error; addBtn.textContent = res.error === 'Already friends' ? t('common.already_friends') : res.error === 'Request already pending' ? t('common.already_sent') : res.error;
addBtn.style.background = '#64748b'; addBtn.style.background = '#64748b';
return; return;
} }
audio.play('reward'); audio.play('reward');
juice.hapticLight(); juice.hapticLight();
addBtn.textContent = '✓ تم الإرسال'; addBtn.textContent = t('common.sent');
addBtn.style.background = '#34D399'; addBtn.style.background = '#34D399';
addBtn.style.borderColor = '#34D399'; addBtn.style.borderColor = '#34D399';
} catch (e) { } catch (e) {
const msg = e.message || ''; const msg = e.message || '';
if (msg.includes('duplicate') || msg.includes('already') || msg.includes('Already')) { if (msg.includes('duplicate') || msg.includes('already') || msg.includes('Already')) {
addBtn.textContent = 'مرسل سابقاً'; addBtn.textContent = t('common.already_sent');
addBtn.style.background = '#64748b'; addBtn.style.background = '#64748b';
} else { } else {
addBtn.textContent = 'فشل'; addBtn.textContent = t('common.failed');
addBtn.disabled = false; addBtn.disabled = false;
setTimeout(() => { addBtn.textContent = '+ أضف'; }, 2000); setTimeout(() => { addBtn.textContent = t('social.add_btn'); }, 2000);
} }
} }
}); });
}); });
} catch (e) { } catch (e) {
results.innerHTML = '<div style="text-align:center;color:#ef4444;">فشل البحث</div>'; results.innerHTML = `<div style="text-align:center;color:#ef4444;">${t('social.search_failed')}</div>`;
} }
}; };
...@@ -618,19 +618,19 @@ async function loadActivity(content) { ...@@ -618,19 +618,19 @@ async function loadActivity(content) {
content.innerHTML = ` content.innerHTML = `
<div style="text-align:center;padding:40px;color:#64748b;"> <div style="text-align:center;padding:40px;color:#64748b;">
<div style="font-size:32px;margin-bottom:8px;opacity:0.5;">${emoji('news', '📰', 32)}</div> <div style="font-size:32px;margin-bottom:8px;opacity:0.5;">${emoji('news', '📰', 32)}</div>
<div>لا توجد أخبار — العب لتظهر أخبارك هنا</div> <div>${t('social.no_activity')}</div>
</div>`; </div>`;
return; return;
} }
const labels = { const labels = {
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'),
tournament_win: 'فاز ببطولة', tournament_win: t('social.tournament_win'),
friend_add: 'أضاف صديقاً جديداً' friend_add: t('social.friend_add')
}; };
const icons = { const icons = {
game_win: '🏆', game_loss: '💔', game_draw: '🤝', game_win: '🏆', game_loss: '💔', game_draw: '🤝',
...@@ -652,19 +652,19 @@ async function loadActivity(content) { ...@@ -652,19 +652,19 @@ async function loadActivity(content) {
</div>`; </div>`;
}).join(''); }).join('');
} catch (e) { } catch (e) {
content.innerHTML = `<div style="text-align:center;color:#64748b;padding:32px;">لا توجد أخبار</div>`; content.innerHTML = `<div style="text-align:center;color:#64748b;padding:32px;">${t('social.no_activity_short')}</div>`;
} }
} }
function timeAgo(dateStr) { function timeAgo(dateStr) {
const diff = Date.now() - new Date(dateStr).getTime(); const diff = Date.now() - new Date(dateStr).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);
if (days < 7) return `منذ ${days} يوم`; if (days < 7) return t('common.days_ago', { n: days });
return new Date(dateStr).toLocaleDateString('ar'); return new Date(dateStr).toLocaleDateString('ar');
} }
......
...@@ -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');
} }
}); });
} }
......
...@@ -18,14 +18,14 @@ export async function mountGroupMembers(el, params = {}) { ...@@ -18,14 +18,14 @@ export async function mountGroupMembers(el, params = {}) {
<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;flex:1;">الأعضاء</h2> <h2 style="font-size:18px;font-weight:700;color:#f8fafc;flex:1;">${t('group.members')}</h2>
${canManage ? `<button class="btn btn-primary" id="add-member-btn" style="min-height:34px;padding:6px 12px;font-size:12px;">+ إضافة</button>` : ''} ${canManage ? `<button class="btn btn-primary" id="add-member-btn" style="min-height:34px;padding:6px 12px;font-size:12px;">+ ${t('group.add')}</button>` : ''}
</div> </div>
<div id="members-list" style="flex:1;overflow-y:auto;padding:12px 16px;"> <div id="members-list" style="flex:1;overflow-y:auto;padding:12px 16px;">
<div style="text-align:center;padding:24px;color:var(--text-secondary);">${t('common.loading')}</div> <div style="text-align:center;padding:24px;color:var(--text-secondary);">${t('common.loading')}</div>
</div> </div>
<div style="padding:12px 16px;border-top:1px solid rgba(255,255,255,0.06);"> <div style="padding:12px 16px;border-top:1px solid rgba(255,255,255,0.06);">
<button class="btn btn-secondary w-full" id="leave-btn" style="min-height:40px;color:var(--error);">مغادرة المجموعة</button> <button class="btn btn-secondary w-full" id="leave-btn" style="min-height:40px;color:var(--error);">${t('group.leave')}</button>
</div> </div>
</div> </div>
`; `;
...@@ -40,7 +40,7 @@ export async function mountGroupMembers(el, params = {}) { ...@@ -40,7 +40,7 @@ export async function mountGroupMembers(el, params = {}) {
container.innerHTML = members.map(m => { container.innerHTML = members.map(m => {
const p = m.profiles || {}; const p = m.profiles || {};
const roleLabel = m.role === 'owner' ? '👑 مالك' : m.role === 'admin' ? '⭐ مشرف' : ''; const roleLabel = m.role === 'owner' ? `👑 ${t('group.owner')}` : m.role === 'admin' ? `⭐ ${t('group.admin')}` : '';
const isMe = m.user_id === myId; const isMe = m.user_id === myId;
const showRemove = canManage && !isMe && m.role !== 'owner'; const showRemove = canManage && !isMe && m.role !== 'owner';
...@@ -50,11 +50,11 @@ export async function mountGroupMembers(el, params = {}) { ...@@ -50,11 +50,11 @@ export async function mountGroupMembers(el, params = {}) {
${p.avatar_url ? `<img src="${p.avatar_url}" style="width:40px;height:40px;object-fit:cover;border-radius:50%;">` : emoji('person', '👤', 18)} ${p.avatar_url ? `<img src="${p.avatar_url}" style="width:40px;height:40px;object-fit:cover;border-radius:50%;">` : emoji('person', '👤', 18)}
</div> </div>
<div style="flex:1;cursor:pointer;" data-profile="${m.user_id}"> <div style="flex:1;cursor:pointer;" data-profile="${m.user_id}">
<div style="font-size:14px;font-weight:500;color:#f8fafc;">${escapeHtml(p.display_name || 'Player')}${isMe ? ' (أنت)' : ''}</div> <div style="font-size:14px;font-weight:500;color:#f8fafc;">${escapeHtml(p.display_name || 'Player')}${isMe ? ` ${t('common.you')}` : ''}</div>
${roleLabel ? `<div style="font-size:11px;color:#64748b;">${roleLabel}</div>` : ''} ${roleLabel ? `<div style="font-size:11px;color:#64748b;">${roleLabel}</div>` : ''}
</div> </div>
${p.is_online ? '<div style="width:8px;height:8px;border-radius:50%;background:#34D399;"></div>' : ''} ${p.is_online ? '<div style="width:8px;height:8px;border-radius:50%;background:#34D399;"></div>' : ''}
${showRemove ? `<button class="btn btn-secondary remove-btn" data-id="${m.user_id}" style="min-height:28px;padding:4px 10px;font-size:11px;color:var(--error);">إزالة</button>` : ''} ${showRemove ? `<button class="btn btn-secondary remove-btn" data-id="${m.user_id}" style="min-height:28px;padding:4px 10px;font-size:11px;color:var(--error);">${t('group.remove')}</button>` : ''}
</div>`; </div>`;
}).join(''); }).join('');
...@@ -72,11 +72,11 @@ export async function mountGroupMembers(el, params = {}) { ...@@ -72,11 +72,11 @@ export async function mountGroupMembers(el, params = {}) {
// Remove buttons // Remove buttons
container.querySelectorAll('.remove-btn').forEach(btn => { container.querySelectorAll('.remove-btn').forEach(btn => {
btn.addEventListener('click', async () => { btn.addEventListener('click', async () => {
const confirmed = await modal.confirm('إزالة هذا العضو من المجموعة؟', { const confirmed = await modal.confirm(t('group.remove_member'), {
title: 'إزالة عضو', title: t('group.remove'),
icon: '👤', icon: '👤',
confirmText: 'إزالة', confirmText: t('group.remove'),
cancelText: 'إلغاء', cancelText: t('common.cancel'),
danger: true danger: true
}); });
if (!confirmed) return; if (!confirmed) return;
...@@ -106,11 +106,11 @@ export async function mountGroupMembers(el, params = {}) { ...@@ -106,11 +106,11 @@ export async function mountGroupMembers(el, params = {}) {
// Leave group // Leave group
el.querySelector('#leave-btn').addEventListener('click', async () => { el.querySelector('#leave-btn').addEventListener('click', async () => {
const confirmed = await modal.confirm('هل تريد مغادرة المجموعة؟', { const confirmed = await modal.confirm(t('group.leave_confirm'), {
title: 'مغادرة', title: t('group.leave'),
icon: '🚪', icon: '🚪',
confirmText: 'غادر', confirmText: t('group.leave_btn'),
cancelText: 'ابقَ', cancelText: t('group.stay'),
danger: true danger: true
}); });
if (!confirmed) return; if (!confirmed) return;
...@@ -134,9 +134,9 @@ async function showAddMemberPicker(el, groupId) { ...@@ -134,9 +134,9 @@ async function showAddMemberPicker(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:20px;width:100%;max-width:400px;max-height:60vh;display:flex;flex-direction:column;gap:12px;"> <div style="background:#1a1a2e;border-radius:20px 20px 0 0;padding:20px;width:100%;max-width:400px;max-height:60vh;display:flex;flex-direction:column;gap:12px;">
<div style="font-size:15px;font-weight:700;color:#f8fafc;text-align:center;">إضافة صديق</div> <div style="font-size:15px;font-weight:700;color:#f8fafc;text-align:center;">${t('mp.add_friend')}</div>
<div id="add-friends-list" style="overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:6px;"> <div id="add-friends-list" style="overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:6px;">
<div style="text-align:center;color:var(--text-secondary);font-size:13px;">جاري التحميل...</div> <div style="text-align:center;color:var(--text-secondary);font-size:13px;">${t('common.loading')}</div>
</div> </div>
<button class="btn btn-secondary w-full" id="close-add">${t('common.close')}</button> <button class="btn btn-secondary w-full" id="close-add">${t('common.close')}</button>
</div> </div>
...@@ -152,7 +152,7 @@ async function showAddMemberPicker(el, groupId) { ...@@ -152,7 +152,7 @@ async function showAddMemberPicker(el, groupId) {
const list = overlay.querySelector('#add-friends-list'); const list = overlay.querySelector('#add-friends-list');
if (!friends.length) { if (!friends.length) {
list.innerHTML = `<div style="text-align:center;color:var(--text-secondary);font-size:13px;">لا يوجد أصدقاء</div>`; list.innerHTML = `<div style="text-align:center;color:var(--text-secondary);font-size:13px;">${t('social.no_friends')}</div>`;
return; return;
} }
...@@ -162,7 +162,7 @@ async function showAddMemberPicker(el, groupId) { ...@@ -162,7 +162,7 @@ async function showAddMemberPicker(el, groupId) {
${f.avatar_url ? `<img src="${f.avatar_url}" style="width:32px;height:32px;object-fit:cover;border-radius:50%;">` : emoji('person', '👤', 14)} ${f.avatar_url ? `<img src="${f.avatar_url}" style="width:32px;height:32px;object-fit:cover;border-radius:50%;">` : emoji('person', '👤', 14)}
</div> </div>
<span style="flex:1;font-size:13px;color:#f8fafc;">${escapeHtml(f.display_name || 'Player')}</span> <span style="flex:1;font-size:13px;color:#f8fafc;">${escapeHtml(f.display_name || 'Player')}</span>
<button class="btn btn-primary add-one-btn" data-id="${f.id}" style="min-height:28px;padding:4px 12px;font-size:11px;">إضافة</button> <button class="btn btn-primary add-one-btn" data-id="${f.id}" style="min-height:28px;padding:4px 12px;font-size:11px;">${t('group.add')}</button>
</div> </div>
`).join(''); `).join('');
...@@ -172,11 +172,11 @@ async function showAddMemberPicker(el, groupId) { ...@@ -172,11 +172,11 @@ async function showAddMemberPicker(el, groupId) {
btn.textContent = '...'; btn.textContent = '...';
try { try {
await net.post('groups.php', { action: 'add-member', group_id: groupId, user_id: btn.dataset.id }); await net.post('groups.php', { action: 'add-member', group_id: groupId, user_id: btn.dataset.id });
btn.textContent = '✓'; btn.textContent = t('common.done');
btn.style.background = 'transparent'; btn.style.background = 'transparent';
btn.style.color = '#34D399'; btn.style.color = '#34D399';
} catch (e) { } catch (e) {
btn.textContent = e.message || 'خطأ'; btn.textContent = e.message || t('social.error');
btn.style.color = 'var(--error)'; btn.style.color = 'var(--error)';
} }
}); });
......
...@@ -9,8 +9,8 @@ export async function mountGroups(el) { ...@@ -9,8 +9,8 @@ export async function mountGroups(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;flex:1;">المجموعات</h2> <h2 style="font-size:18px;font-weight:700;color:#f8fafc;flex:1;">${t('social.groups_tab')}</h2>
<button class="btn btn-primary" id="create-btn" style="min-height:34px;padding:6px 14px;font-size:12px;">+ إنشاء</button> <button class="btn btn-primary" id="create-btn" style="min-height:34px;padding:6px 14px;font-size:12px;">+ ${t('group.create')}</button>
</div> </div>
<div id="groups-list" style="flex:1;overflow-y:auto;padding:12px 16px;"> <div id="groups-list" style="flex:1;overflow-y:auto;padding:12px 16px;">
<div style="text-align:center;padding:24px;color:var(--text-secondary);">${t('common.loading')}</div> <div style="text-align:center;padding:24px;color:var(--text-secondary);">${t('common.loading')}</div>
...@@ -30,8 +30,8 @@ export async function mountGroups(el) { ...@@ -30,8 +30,8 @@ export async function mountGroups(el) {
container.innerHTML = ` container.innerHTML = `
<div style="text-align:center;padding:48px 24px;color:var(--text-secondary);"> <div style="text-align:center;padding:48px 24px;color:var(--text-secondary);">
<div style="font-size:40px;margin-bottom:12px;">${emoji('group', '👥', 40)}</div> <div style="font-size:40px;margin-bottom:12px;">${emoji('group', '👥', 40)}</div>
<div style="font-size:14px;">لا توجد مجموعات بعد</div> <div style="font-size:14px;">${t('group.no_groups')}</div>
<div style="font-size:12px;margin-top:4px;">أنشئ مجموعة وادعُ أصدقائك!</div> <div style="font-size:12px;margin-top:4px;">${t('group.no_groups_hint')}</div>
</div>`; </div>`;
return; return;
} }
...@@ -43,7 +43,7 @@ export async function mountGroups(el) { ...@@ -43,7 +43,7 @@ export async function mountGroups(el) {
</div> </div>
<div style="flex:1;min-width:0;"> <div style="flex:1;min-width:0;">
<div style="font-size:14px;font-weight:600;color:#f8fafc;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(g.name)}</div> <div style="font-size:14px;font-weight:600;color:#f8fafc;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${escapeHtml(g.name)}</div>
<div style="font-size:12px;color:#64748b;">${g.member_count || 1} أعضاء • ${g.my_role === 'owner' ? 'مالك' : g.my_role === 'admin' ? 'مشرف' : 'عضو'}</div> <div style="font-size:12px;color:#64748b;">${g.member_count || 1} ${t('group.members')}${g.my_role === 'owner' ? t('group.owner') : g.my_role === 'admin' ? t('group.admin') : t('profile.member')}</div>
</div> </div>
<span style="color:#64748b;font-size:16px;">←</span> <span style="color:#64748b;font-size:16px;">←</span>
</div> </div>
......
...@@ -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 });
} }
...@@ -14,14 +14,14 @@ export async function mountTournamentsHub(el) { ...@@ -14,14 +14,14 @@ export async function mountTournamentsHub(el) {
<div style="display:flex;flex-direction:column;height:100%;background:#0a0a1a;"> <div style="display:flex;flex-direction:column;height:100%;background:#0a0a1a;">
<div style="padding:14px 16px 0;background:#0f0f1e;"> <div style="padding:14px 16px 0;background:#0f0f1e;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;"> <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
<h2 style="font-size:20px;font-weight:800;color:#f8fafc;">${emoji('tournament_cup', '🏆', 20)} البطولات</h2> <h2 style="font-size:20px;font-weight:800;color:#f8fafc;">${emoji('tournament_cup', '🏆', 20)} ${t('tournament.hub_title')}</h2>
</div> </div>
<div id="tour-filters" style="display:flex;gap:6px;overflow-x:auto;padding-bottom:12px;border-bottom:1px solid rgba(255,255,255,0.04);"> <div id="tour-filters" style="display:flex;gap:6px;overflow-x:auto;padding-bottom:12px;border-bottom:1px solid rgba(255,255,255,0.04);">
<button class="tour-filter active" data-filter="all">الكل</button> <button class="tour-filter active" data-filter="all">${t('common.all')}</button>
<button class="tour-filter" data-filter="my">بطولاتي</button> <button class="tour-filter" data-filter="my">${t('tournament.my_tournaments')}</button>
<button class="tour-filter" data-filter="registration">تسجيل مفتوح</button> <button class="tour-filter" data-filter="registration">${t('tournament.registration_open')}</button>
<button class="tour-filter" data-filter="in_progress">جارية</button> <button class="tour-filter" data-filter="in_progress">${t('tournament.active')}</button>
<button class="tour-filter" data-filter="completed">منتهية</button> <button class="tour-filter" data-filter="completed">${t('tournament.completed')}</button>
</div> </div>
</div> </div>
<div id="tour-hub-content" style="flex:1;overflow-y:auto;padding:14px 16px;"></div> <div id="tour-hub-content" style="flex:1;overflow-y:auto;padding:14px 16px;"></div>
...@@ -92,17 +92,17 @@ async function loadTournaments(el) { ...@@ -92,17 +92,17 @@ async function loadTournaments(el) {
if (tournaments.length === 0) { if (tournaments.length === 0) {
const emptyMessages = { const emptyMessages = {
all: 'لا توجد بطولات حالياً', all: t('tournament.no_tournaments'),
my: 'لم تسجّل في أي بطولة بعد', my: t('tournament.no_my_tournaments'),
registration: 'لا توجد بطولات مفتوحة للتسجيل', registration: t('tournament.no_open_tournaments'),
in_progress: 'لا توجد بطولات جارية', in_progress: t('tournament.no_active_tournaments'),
completed: 'لا توجد بطولات منتهية' completed: t('tournament.no_completed_tournaments')
}; };
content.innerHTML = ` content.innerHTML = `
<div style="text-align:center;padding:48px 20px;"> <div style="text-align:center;padding:48px 20px;">
<div style="font-size:48px;margin-bottom:12px;opacity:0.4;">${emoji('tournament_cup', '🏆', 48)}</div> <div style="font-size:48px;margin-bottom:12px;opacity:0.4;">${emoji('tournament_cup', '🏆', 48)}</div>
<div style="font-size:15px;font-weight:600;color:#f8fafc;margin-bottom:6px;">${emptyMessages[activeFilter] || emptyMessages.all}</div> <div style="font-size:15px;font-weight:600;color:#f8fafc;margin-bottom:6px;">${emptyMessages[activeFilter] || emptyMessages.all}</div>
<div style="font-size:12px;color:#64748b;">ستظهر البطولات الجديدة هنا</div> <div style="font-size:12px;color:#64748b;">${t('tournament.new_tournaments_here')}</div>
</div> </div>
`; `;
return; return;
...@@ -120,7 +120,7 @@ async function loadTournaments(el) { ...@@ -120,7 +120,7 @@ async function loadTournaments(el) {
<div class="tour-hub-card ${isRegistered ? 'registered' : ''} ${hasPending ? 'has-pending' : ''}" data-id="${tour.id}"> <div class="tour-hub-card ${isRegistered ? 'registered' : ''} ${hasPending ? 'has-pending' : ''}" data-id="${tour.id}">
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px;"> <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px;">
<div style="flex:1;min-width:0;"> <div style="flex:1;min-width:0;">
<div style="font-size:15px;font-weight:700;color:#f8fafc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${tour.name || 'بطولة'}</div> <div style="font-size:15px;font-weight:700;color:#f8fafc;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${tour.name || t('tournament.title')}</div>
<div style="font-size:11px;color:#64748b;margin-top:2px;">${formatGame(tour.game_key)} · ${formatName(tour.format)}</div> <div style="font-size:11px;color:#64748b;margin-top:2px;">${formatGame(tour.game_key)} · ${formatName(tour.format)}</div>
</div> </div>
<span class="tour-status-pill" style="background:${getStatusColor(tour.status)};color:${tour.status === 'in_progress' ? '#000' : '#fff'};">${getStatusLabel(tour.status)}</span> <span class="tour-status-pill" style="background:${getStatusColor(tour.status)};color:${tour.status === 'in_progress' ? '#000' : '#fff'};">${getStatusLabel(tour.status)}</span>
...@@ -129,19 +129,19 @@ async function loadTournaments(el) { ...@@ -129,19 +129,19 @@ async function loadTournaments(el) {
<div style="display:flex;gap:12px;margin-bottom:10px;"> <div style="display:flex;gap:12px;margin-bottom:10px;">
<div class="tour-stat"> <div class="tour-stat">
<span class="tour-stat-val" style="color:#3B82F6;">${tour.player_count || 0}<span style="font-size:11px;color:#64748b;">/${tour.max_players || '∞'}</span></span> <span class="tour-stat-val" style="color:#3B82F6;">${tour.player_count || 0}<span style="font-size:11px;color:#64748b;">/${tour.max_players || '∞'}</span></span>
<span class="tour-stat-label">لاعبين</span> <span class="tour-stat-label">${t('tournament.players_label')}</span>
</div> </div>
<div class="tour-stat"> <div class="tour-stat">
<span class="tour-stat-val" style="color:#E4AC38;">${tour.swiss_rounds || tour.rounds_total || '?'}</span> <span class="tour-stat-val" style="color:#E4AC38;">${tour.swiss_rounds || tour.rounds_total || '?'}</span>
<span class="tour-stat-label">جولات</span> <span class="tour-stat-label">${t('tournament.rounds_label')}</span>
</div> </div>
<div class="tour-stat"> <div class="tour-stat">
<span class="tour-stat-val" style="color:#10B981;">${formatTimeControl(tour.time_control)}</span> <span class="tour-stat-val" style="color:#10B981;">${formatTimeControl(tour.time_control)}</span>
<span class="tour-stat-label">الوقت</span> <span class="tour-stat-label">${t('tournament.time_label')}</span>
</div> </div>
${tour.prize_pool_coins ? `<div class="tour-stat"> ${tour.prize_pool_coins ? `<div class="tour-stat">
<span class="tour-stat-val" style="color:#F59E0B;">${tour.prize_pool_coins}</span> <span class="tour-stat-val" style="color:#F59E0B;">${tour.prize_pool_coins}</span>
<span class="tour-stat-label">${emoji('coin', '🪙', 10)} جائزة</span> <span class="tour-stat-label">${emoji('coin', '🪙', 10)} ${t('tournament.prize')}</span>
</div>` : ''} </div>` : ''}
</div> </div>
...@@ -149,13 +149,13 @@ async function loadTournaments(el) { ...@@ -149,13 +149,13 @@ async function loadTournaments(el) {
${hasPending ? ` ${hasPending ? `
<div style="background:rgba(228,172,56,0.1);border:1px solid rgba(228,172,56,0.3);border-radius:10px;padding:10px;display:flex;align-items:center;justify-content:space-between;"> <div style="background:rgba(228,172,56,0.1);border:1px solid rgba(228,172,56,0.3);border-radius:10px;padding:10px;display:flex;align-items:center;justify-content:space-between;">
<div style="font-size:12px;color:#E4AC38;font-weight:600;">${emoji('swords', '⚔️', 12)} مباراتك جاهزة — ج${pendingMatch?.round_number || ''} vs ${pendingMatch?.opponent_name || 'خصم'}</div> <div style="font-size:12px;color:#E4AC38;font-weight:600;">${emoji('swords', '⚔️', 12)} ${t('tournament.match_ready')}${t('tournament.round_short')}${pendingMatch?.round_number || ''} vs ${pendingMatch?.opponent_name || t('tournament.opponent')}</div>
<button class="play-now-btn" data-tid="${tour.id}" data-rid="${pendingMatch?.round_id}" data-idx="${pendingMatch?.pairing_index}" style="background:#E4AC38;border:none;border-radius:8px;padding:6px 14px;color:#000;font-weight:700;font-size:11px;cursor:pointer;">العب</button> <button class="play-now-btn" data-tid="${tour.id}" data-rid="${pendingMatch?.round_id}" data-idx="${pendingMatch?.pairing_index}" style="background:#E4AC38;border:none;border-radius:8px;padding:6px 14px;color:#000;font-weight:700;font-size:11px;cursor:pointer;">${t('common.play')}</button>
</div> </div>
` : isRegistered ? ` ` : isRegistered ? `
<div style="font-size:11px;color:#34D399;font-weight:600;">${emoji('checkmark', '✓', 11)} مسجّل</div> <div style="font-size:11px;color:#34D399;font-weight:600;">${emoji('checkmark', '✓', 11)} ${t('tournament.joined')}</div>
` : tour.status === 'registration' ? ` ` : tour.status === 'registration' ? `
<button class="register-btn" data-tid="${tour.id}" style="width:100%;background:#E4AC38;border:none;border-radius:10px;padding:12px;color:#000;font-weight:700;font-size:13px;cursor:pointer;margin-top:4px;">${emoji('swords', '⚔️', 13)} سجّل الآن${tour.entry_fee_coins ? ' — ' + tour.entry_fee_coins + ' عملة' : ''}</button> <button class="register-btn" data-tid="${tour.id}" style="width:100%;background:#E4AC38;border:none;border-radius:10px;padding:12px;color:#000;font-weight:700;font-size:13px;cursor:pointer;margin-top:4px;">${emoji('swords', '⚔️', 13)} ${t('tournament.register_now')}${tour.entry_fee_coins ? ' — ' + tour.entry_fee_coins + ' ' + t('common.coins') : ''}</button>
` : ''} ` : ''}
</div> </div>
`; `;
...@@ -174,16 +174,16 @@ async function loadTournaments(el) { ...@@ -174,16 +174,16 @@ async function loadTournaments(el) {
e.stopPropagation(); e.stopPropagation();
audio.play('click'); audio.play('click');
btn.disabled = true; btn.disabled = true;
btn.textContent = 'جاري التسجيل...'; btn.textContent = t('tournament.registering');
try { try {
await net.post('tournaments.php', { action: 'register', tournament_id: btn.dataset.tid }); await net.post('tournaments.php', { action: 'register', tournament_id: btn.dataset.tid });
btn.textContent = '✓ تم التسجيل'; btn.textContent = '✓ ' + t('tournament.registered');
btn.style.background = '#34D399'; btn.style.background = '#34D399';
btn.closest('.tour-hub-card')?.classList.add('registered'); btn.closest('.tour-hub-card')?.classList.add('registered');
} catch (err) { } catch (err) {
btn.textContent = err.message || 'فشل'; btn.textContent = err.message || t('common.failed');
btn.disabled = false; btn.disabled = false;
setTimeout(() => { btn.textContent = 'سجّل الآن'; }, 2000); setTimeout(() => { btn.textContent = t('tournament.register_now'); }, 2000);
} }
}); });
}); });
...@@ -214,7 +214,7 @@ async function loadTournaments(el) { ...@@ -214,7 +214,7 @@ async function loadTournaments(el) {
recovered: data.already_exists && data.status === 'in_progress' recovered: data.already_exists && data.status === 'in_progress'
}); });
} catch (err) { } catch (err) {
btn.textContent = 'فشل'; btn.textContent = t('common.failed');
btn.disabled = false; btn.disabled = false;
} }
}); });
...@@ -223,8 +223,8 @@ async function loadTournaments(el) { ...@@ -223,8 +223,8 @@ async function loadTournaments(el) {
} catch (e) { } catch (e) {
content.innerHTML = ` content.innerHTML = `
<div style="text-align:center;padding:32px;color:#ef4444;"> <div style="text-align:center;padding:32px;color:#ef4444;">
<div style="margin-bottom:8px;">فشل تحميل البطولات</div> <div style="margin-bottom:8px;">${t('tournament.load_failed')}</div>
<button class="btn btn-secondary" id="retry-tour" style="font-size:12px;padding:8px 16px;">حاول مرة أخرى</button> <button class="btn btn-secondary" id="retry-tour" style="font-size:12px;padding:8px 16px;">${t('common.retry')}</button>
</div> </div>
`; `;
content.querySelector('#retry-tour')?.addEventListener('click', () => loadTournaments(el)); content.querySelector('#retry-tour')?.addEventListener('click', () => loadTournaments(el));
...@@ -242,28 +242,28 @@ function getStatusColor(status) { ...@@ -242,28 +242,28 @@ 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: 'أرينا', group_stage: 'مجموعات' }; 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'), group_stage: t('tournament.format_group_stage') };
return names[format] || format || ''; return names[format] || format || '';
} }
function formatGame(key) { function formatGame(key) {
const games = { chess: 'شطرنج', ludo: 'لودو', domino: 'دومينو' }; const games = { chess: t('game.chess'), ludo: t('game.ludo'), domino: t('game.domino') };
return games[key] || key || 'شطرنج'; return games[key] || key || t('game.chess');
} }
function formatTimeControl(tc) { function formatTimeControl(tc) {
if (!tc) return '?'; if (!tc) return '?';
const parts = tc.split('_'); const parts = tc.split('_');
if (parts.length >= 2) return parts[1] + (parts[2] && parts[2] !== '0' ? '+' + parts[2] : '') + 'د'; if (parts.length >= 2) return parts[1] + (parts[2] && parts[2] !== '0' ? '+' + parts[2] : '') + t('common.minutes_abbr');
return tc; return tc;
} }
...@@ -275,12 +275,12 @@ function formatDate(dateStr) { ...@@ -275,12 +275,12 @@ function formatDate(dateStr) {
if (diff > 0 && diff < 86400000) { if (diff > 0 && diff < 86400000) {
const hours = Math.floor(diff / 3600000); const hours = Math.floor(diff / 3600000);
const mins = Math.floor((diff % 3600000) / 60000); const mins = Math.floor((diff % 3600000) / 60000);
if (hours > 0) return `تبدأ بعد ${hours} ساعة`; if (hours > 0) return t('tournament.starts_in_hours', { n: hours });
return `تبدأ بعد ${mins} دقيقة`; return t('tournament.starts_in_minutes', { n: mins });
} }
if (diff > 0 && diff < 7 * 86400000) { if (diff > 0 && diff < 7 * 86400000) {
const days = Math.ceil(diff / 86400000); const days = Math.ceil(diff / 86400000);
return `بعد ${days} يوم`; return t('tournament.in_days', { n: days });
} }
return d.toLocaleDateString('ar', { day: 'numeric', month: 'short', year: d.getFullYear() !== now.getFullYear() ? 'numeric' : undefined }); return d.toLocaleDateString('ar', { day: 'numeric', month: 'short', year: d.getFullYear() !== now.getFullYear() ? 'numeric' : undefined });
} }
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