فاتحة باب الانضمام الآن — اضغط «انضمام» ويوصل طلبك لرئيسها
اطلب تحدي
اختر خصمك ونمط اللعب — ويوصل طلبك لكل الكلانات
⏸️ التأجيل — لرئيس/نائب أي كلان إرسال طلب تأجيل، والكلان الآخر يقبل أو يرفض. إذا انتهت مدة التأجيل (48 ساعة) دون استئناف مشترك ← يُلغى التحدي بدون نتيجة.
⚙️ التأجيل متاح بعد (اختياري):قيم
🏅 المصنّف — النقاط تزيد بالفوز وتنقص بالخسارة. الفوز 4-0 أكثر من 4-3، والفوز على كلان أقوى منك يعطيك أضعاف.
تسجيل جديد
أنشئ حسابك للانضمام للكلانات والتحديات
△○✕□
🔑 آيدي السوني هو هويتك في المنصة — به يضيفك الخصم وقت التحدي، وبه يربطك رئيس كلانك. ⚠️ غير قابل للتغيير بعد الحفظ — تأكّد من كتابته حرفاً حرفاً قبل ما تكمل.
أو
⚠️ لو اخترت «إخفاء بريدي» فاثبت عليه دائماً — تبديله ينشئ حساباً ثانياً منفصلاً
⚡ الأسرع — بدون بريد ولا كلمة سر 🎉 وتنضم أيضاً لسيرفر Discord تلقائياً
أكمل القائمة ويترسّم كلانك في نفس اللحظة — بلا انتظار موافقة
1 بيانات الكلان
CLAN ____ | COD-AR
2 قائمة اللاعبين (0/6)
🔑 آيدي السوني إجباري لكل لاعب — هو هويته بالمنصة.
أي لاعب يسجّل لاحقاً بنفس الآيدي يرتبط بكلانك تلقائياً، وبه يتواصل معكم الخصم وقت التحدي.
تأكّد من كتابته صح — الخطأ فيه يعطّل ارتباط العضو.
إدارة الكلان
النائب · الأعضاء · الوصف · اللوقو
🛍️ مميزات الباقة
🎨 لون الكلان
يحتاج ردّك
📥 طلبات الانضمام
الأعضاء
هوية الكلان
🛡️
من ألبوم جوالك — يتخزّن على سيرفرنا
خطر
سجلّكم لا يُمحى — نتائجكم تبقى بالسجل العام وتاقكم محجوز لكم.
🎖️ الرتب والصلاحيات
عيّن منازل أعضائك — الرئيس واحد، والنائب يده معه
👥 أعضاء الكلان
👑 الرئيس — يملك كل شيء: التحديات، توثيق النتائج، الرتب، وأرشفة الكلان. واحدٌ لا غير. 🎖️ النائب — يطلق التحديات ويردّ عليها ويستقبل طلبات الانضمام، ولا يمسّ الرتب. 🎮 لاعب — يمثّل الكلان في المباريات ويظهر في كشفه. 📋 منسق كلان — يدير طلبات التحديات ويوثّق نتائجها نيابةً عن الرئيس. متاح لأصحاب باقة GOLD أو DIAMOND.
?CLANكلانك
VS
?CLANالخصم
⚔️ انطلق التحدي
جهّزوا فريقكم
?
عضو
⚔️قوانين تحديات الكلانات — سنايبر
النسخة المعتمدة كاملةً · تُطبَّق على كل تحدٍّ مصنّف · وقرار الإدارة نافذ فيما وراءها
4أقيام للحسم
8راوندات بالقيم
0.5ثحدّ الزوم
10دحدّ المهلة
🎨 العقوبة تُقرأ من لونها:راوند للمتضرر ·
القيم كاملاً ·
التحدي كاملاً ·
استبعاد لاعب ·
قاعدة / حق
⏳ جاري التحميل...
❓الأسئلة الشائعة
شرح مصوّر لأهم خطوات استخدام المنصة — اضغط أي سؤال ليفتح الفيديو
🛡️ إدارة الكلان
طريقة إضافة الأعضاء للكلان، منح الصلاحيات داخل الكلان، وفتح باب الانضمام.
🎯 كيف تطلب تحدي
خطوات إطلاق طلب تحدي جديد لكلان آخر.
🛡️ الإشراف والتوثيق
كيف تطلب مشرفاً، تُبلّغ عن مخالفة (زي الحرق)، وتوثّق نتيجة تحدي.
🏅 نظام التصنيف والرتب
كيف تُحسب النقاط، وكيف يترقّى كلانك عبر مستويات الرتب.
🛍️
متجر COD·AR
باقات حصرية تمنحك مزايا إضافية داخل المنصة
⏳ جاري التحميل...
🎫 التواصل مع الإدارة
شكوى · اقتراح · طلب — والردّ يصلك على نفس التذكرة
⏱️ التذكرة صالحة لمدة 30 يوم
📎 الصور والمقاطع تُرفق داخل المحادثة بعد فتح التذكرة.
🗂️ تذاكري
🎖️ التقديم على الإشراف
🔒 التقديم على الإشراف مغلق حالياً سيتم الإعلان عند فتح باب التقديم
';
document.body.appendChild(bk); document.body.appendChild(bx);
var close=function(v){
document.removeEventListener('keydown',key);
bk.remove(); bx.remove(); done(v);
};
var key=function(e){ if(e.key==='Escape') close(false); if(e.key==='Enter') close(true); };
document.addEventListener('keydown',key);
bk.onclick=function(){ close(false); };
bx.querySelector('#cfNo').onclick=function(){ close(false); };
bx.querySelector('#cfYes').onclick=function(){ close(true); };
try{ bx.querySelector('#cfYes').focus(); }catch(e){}
});
};
/* ═══ شارة الرتبة ═══
اسم المجموعة نفسها يصير الشارة، بلونها وأيقونتها — فيُدار من اللوحة
بدل ما يكون محفوراً في الكود. تأخذ لمحةً من إطارها كي تنسجم معه. */
/* ═══ أيقونةُ الشارة: حرفٌ أو صورة ═══
كانت نصّاً وحدَه، فمن أرادَ شارةً مرسومةً لم يجدْ باباً. والحقلُ
نفسُه يحملُ الاثنَين: ما بدأَ بـ`/` أو `http` صورةٌ، وما سواه حرف.
**بابٌ واحدٌ يقبلُ الشكلَين أهونُ من حقلٍ ثانٍ يُنسى نصفُ ملئِه.**
والصورةُ تُعرَضُ بحجمِ السطرِ نفسِه فتكبرُ وتصغرُ معه بلا ضبطٍ
في كلِّ موضع — فهي في الدردشةِ صغيرةٌ وفي الملفِّ أكبر، ورقمٌ ثابتٌ
كان سيُفسِدُ أحدَهما. */
window.fxBadgeIcon=function(v,c){
v=String(v||'');
if(!v) return '';
if(/^(https?:)?\/\//.test(v) || v.charAt(0)==='/')
return '';
return esc(v)+' ';
};
window.fxBadge=function(fx,sz){
if(!fx||!fx.badge_on||!fx.badge_name) return '';
var c=fx.badge_color||'#38b6ff', k=fx.badge_preset;
var cl=(k&&k!=='none')?(' fxb fxb-'+k+(window.FXB&&window.FXB[k]?' '+window.FXB[k]:'')):'';
return ''+window.fxBadgeIcon(fx.badge_icon,c)+esc(fx.badge_name)+'';
};
/* اسم مصمَّم — نفس منطق اللوحة */
window.fxName=function(fx,name,click){
if(!fx) return esc(name||'لاعب');
var st='font-weight:800;';
if(fx.grad) st+='background:'+fx.grad+';-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;';
else st+='color:'+esc(fx.color)+';';
if(fx.glow&&!fx.grad) st+='text-shadow:'+fx.glow+';';
if(click) st+='cursor:pointer;';
return ''+
(fx.icon?esc(fx.icon)+' ':'')+esc(name||'لاعب')+'';
};
/* توافقاً مع النداءات القائمة */
window.nsEl=function(sid,name,click){ return window.fxName(window.fxOf(null,sid),name,click); };
window.nsBg=function(sid){ var s=sid?window._nsMap[sid]:null; return (s&&s.bg_effect)||null; };
/* ═══ تغيّرُ إعدادٍ ليس سبباً لهدم الشاشة ═══
كانت هذه تُنادي refreshNow — إعادةَ تحميلٍ كاملة تتجاوز المهدّئ.
فأي كتابةٍ في settings تُعيد بناء شاشة **كل** متصلٍ في اللحظة
نفسها: عشرون متصلاً × أحد عشر استعلاماً من كتابةٍ واحدة.
والإعدادات لا تغيّر بيانات الشاشة أصلاً — تغيّر حدوداً ونصوصاً
تُطبَّق بلا جلبٍ جديد. فنكتفي بتطبيقها، ونُهدّئ ما تبقّى. */
var _gsfT=null;
window.getSettingsForce=function(){
if(_gsfT) return; /* نداءاتٌ متلاحقة ⟵ واحدة */
_gsfT=setTimeout(function(){
_gsfT=null;
getSettings(true).then(function(){
try{ applyAppSettings(!!window._isStaff); }catch(e){}
try{ if(window.applyAddNote) window.applyAddNote(); }catch(e){}
/* المهدَّأة لا الفورية — وإن لم تتغيّر بياناتُ الشاشة فلا شيء */
try{ if(window.refreshActive) window.refreshActive(); }catch(e){}
},function(){});
}, 800);
};
function applyAppSettings(staff){
getSettings().then(function(m){
/* لا يُلحَقُ اسمٌ محفورٌ بعدَ اليوم — الاسمُ من window.BRAND وحدَه،
وعنوانُ الشاشةِ يُبنى في go() فلا يُزاحَمُ من هنا. */
if(m.site_name){ try{ window.BRAND = String(m.site_name).trim() || window.BRAND; }catch(e){} }
if(m.discord_invite && window.setDiscordInvite) window.setDiscordInvite(m.discord_invite);
var ov=document.getElementById('maintOverlay');
if(m.maintenance==='1' && !staff){
if(!ov){ ov=document.createElement('div'); ov.id='maintOverlay'; ov.style.cssText='position:fixed;inset:0;z-index:9999;background:#04070b;color:#eaf2fb;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:30px;gap:14px;font-family:inherit'; ov.innerHTML='
🔧
المنصة في الصيانة
نعمل على تحسينات — نرجع بأقرب وقت 🎮
'; document.body.appendChild(ov); }
} else if(ov){ ov.remove(); }
if(window.applyTicker) window.applyTicker(m);
if(window.applyCountdown) window.applyCountdown(m);
var off = m.chat_off==='1'; window._chatOff=off;
if($('chatInput')){ $('chatInput').disabled=off; $('chatInput').placeholder= off?'الدردشة موقوفة مؤقتاً':'اكتب رسالة...'; }
if(window.applyRushVis) window.applyRushVis();
},function(){});
}
function refreshAuth(){
/* getUser يضرب الشبكة؛ لو تعثّرت (فتح بارد داخل التطبيق) نرجع للجلسة
المخزّنة محلياً كي لا تبقى الترويسة «ضيف» وبقية الصفحات تعرف صاحبها. */
window.authUser().then(_authApply, function(){
sb.auth.getSession().then(function(r){
var u=r&&r.data&&r.data.session&&r.data.session.user;
if(u) _authApply({data:{user:u}});
},function(){});
});
}
function _authApply(res){
(function(){
var user = res && res.data && res.data.user;
/* ═══ الصلاحيات تُقرأ بعد أن تُعرف الهوية ═══
كانت applyPerms تُنادى مرّةً عند الإقلاع، وقد تسبق إرفاقَ
الجلسة بالطلب — فتعود بصلاحيات زائر، والزائر لا يرى الدردشة،
فتُخفى عن العضو ولا شيء يعيدها إلا إعادة تحميلٍ كاملة.
نُعيدها كلّما تبدّلت الهوية: من ضيفٍ إلى عضو أو بالعكس. */
var uidBefore = window._uid || null;
window._uid = user?user.id:null;
if(uidBefore !== window._uid){
clearTimeout(window._apT);
window._apT=setTimeout(function(){ if(window.applyPerms) window.applyPerms(); }, 120);
}
/* ارسم الهوية فوراً من بيانات الحساب — الملف الشخصي يحدّثها بعد لحظة.
بدون هذا تبقى الشريحة «ضيف» لو تأخّر استعلام profiles أو فشل. */
if(user && window.renderIdentity){
var _md=user.user_metadata||{};
window.renderIdentity({ id:user.id, role:'user',
username:(_md.username||_md.full_name||_md.name||_md.user_name||_md.preferred_username||(user.email||'').split('@')[0]),
avatar_url:_md.avatar_url||_md.picture||null });
}
if(!user){ window._banned=false; window._authDone=true; applyAppSettings(false); if(window.renderIdentity) window.renderIdentity(null); }
if($('navAdmin')) $('navAdmin').hidden=true;
if($('menuAdmin')) $('menuAdmin').hidden=true;
if($('moreAdmin')) $('moreAdmin').hidden=true;
if($('menuArchive')) $('menuArchive').hidden=true;
if($('moreArchive')) $('moreArchive').hidden=true;
if($('authView')) $('authView').hidden = !!user;
if($('userView')) $('userView').hidden = !user;
if(user){
$('uEmail').textContent = user.email || '';
var md=user.user_metadata||{};
var fb=(md.username||md.full_name||md.name||md.user_name||md.preferred_username||(user.email||'').split('@')[0]);
sb.from('profiles').select('username,psn_id,avatar_url,banned,seeking_clan,discord_id,discord_name,bio,title,role').eq('id',user.id).single().then(function(pr){
var uname=(pr.data&&pr.data.username)||fb; window._myUsername=uname;
var staff=(pr.data&&(pr.data.role==='admin'||pr.data.role==='moderator'));
window._banned=!!(pr.data&&pr.data.banned); window._isStaff=staff; window._authDone=true; applyAppSettings(staff); if(window.updateBellBadge) window.updateBellBadge(); if(window.linkPushToMe) window.linkPushToMe();
/* من أضافه رئيس كلان بآيديه قبل تسجيله: يرتبط بصفّه الآن.
مرّةً واحدة لكل جلسة لا كلّ تحميلٍ للهوية — كان يُنفَّذ آلافاً
بلا داعٍ (عضوٌ مرتبطٌ سلفاً يعود دائماً بصفر). */
if((pr.data&&pr.data.psn_id) && !window._psnClaimed){
window._psnClaimed=true;
sb.rpc('claim_psn_memberships').then(function(cr){
if(cr&&cr.data>0){ toast('🛡️ ارتبط حسابك بعضويتك في الكلان'); if(window.refreshNow) window.refreshNow(); }
},function(){ window._psnClaimed=false; });
}
if(window.loadInvites) window.loadInvites();
if(window.fillAccountId) window.fillAccountId();
if(window.checkPushReady) window.checkPushReady(); if(window._banned) toast('🚫 حسابك محظور من النشر — راجع الإدارة');
// ربط تلقائي مع Discord لمن يسجّل دخوله عبره (يفعّل أوامر البوت فوراً)
try{
var ids=(user.identities||[]).filter(function(x){return x.provider==='discord';});
var did=ids.length?String(ids[0].id):'';
if(did && window.joinDiscordServer) window.joinDiscordServer();
if(did && !pr.data.discord_id){
sb.from('profiles').update({discord_id:did, discord_name:(md.full_name||md.name||md.user_name||null)}).eq('id',user.id).then(function(){
toast('🔗 تم ربط حسابك بـDiscord — أوامر البوت جاهزة');
sb.rpc('claim_discord_memberships',{p_uid:user.id,p_discord:did}).then(function(r){ if(r&&r.data>0){ toast('🛡️ استُعيدت عضويتك بالكلان'); if(window.refreshNow) window.refreshNow(); } },function(){});
},function(){});
}
}catch(e){}
/* ═══ لوحة الإدارة داخل التطبيق ═══
كانت تظهر لكل من رتبتُه مشرفٌ أو أدمن. صارت بمفتاحها
panel_app: فمن أردتَه يعمل من التطبيق وحده تمنحه إياه بلا
رتبة، ومن أردتَه في اللوحة الخارجية فقط تسحبه منه.
نُظهرها أولاً بالرتبة ثم نصحّحها متى وصلت المفاتيح. */
var showAdm=function(v){
['navAdmin','menuAdmin','moreAdmin'].forEach(function(k){ if($(k)) $(k).hidden=!v; });
};
showAdm(staff);
sb.rpc('my_perms').then(function(pp){
var P=(pp&&pp.data)||{}; window._perms=P;
showAdm(P.panel_app===true || P.admin_all===true || (staff && P.panel_app===undefined));
},function(){});
/* الأرشيف صار أداة إشراف — لا يظهر لعامة الأعضاء */
if($('menuArchive')) $('menuArchive').hidden=!staff;
if($('moreArchive')) $('moreArchive').hidden=!staff;
/* المتجر — نسخة تجريبية، أدمن فقط (مو حتى المشرفين حالياً) */
if($('dwStoreBtn')) $('dwStoreBtn').style.display='';
if($('uName')) $('uName').textContent='أهلاً '+uname;
// شريحة الاسم أعلى التطبيق + رأس القائمة الجانبية
if(window.renderIdentity){
var idp=pr.data||{}; idp.username=uname; idp.id=user.id;
sb.from('profiles').select('points').eq('id',user.id).single()
.then(function(pt){ idp.points=(pt.data&&pt.data.points)||0; window.renderIdentity(idp); },
function(){ window.renderIdentity(idp); });
}
if($('pfUser')){ $('pfUser').value=(pr.data&&pr.data.username)||fb||''; $('pfUser').disabled=true; }
var hasPsn=!!((pr.data&&pr.data.psn_id||'').trim());
var lockPsn = hasPsn && !staff;
if($('pfPsn')){ $('pfPsn').value=(pr.data&&pr.data.psn_id)||''; $('pfPsn').disabled=lockPsn; }
if($('pfPsnLock')) $('pfPsnLock').hidden=!lockPsn;
/* عضو جديد بلا آيدي سوني: تنبيه بارز — هو مفتاح كل شي بالمنصة */
var pw=$('psnWarn');
if(pw){
pw.hidden=hasPsn;
if(!hasPsn) pw.innerHTML=
'
'+
'🔑 خطوتك الأولى — آيدي السوني'+
'
'+
'اكتب PSN ID في الخانة تحت واضغط «حفظ الملف». بدونه: '+
'• ما تقدر تسجّل كلاناً ولا تكون رئيساً '+
'• ما تقدر تطلق تحدياً فردياً '+
'• ما يقدر أحد يضيفك على السوني وقت المباراة
'+
''+
'
';
}
var av=(pr.data&&pr.data.avatar_url)||'';
if(!av && (md.avatar_url||md.picture)){ av=md.avatar_url||md.picture; sb.from('profiles').update({avatar_url:av}).eq('id',user.id).then(function(){},function(){}); }
if($('pfAvatarInit')) $('pfAvatarInit').textContent=(uname||'?').slice(0,1).toUpperCase();
if(av){ if($('pfAvatarImg')){ $('pfAvatarImg').src=av; $('pfAvatarImg').style.display='block'; } if($('pfAvatarInit')) $('pfAvatarInit').style.display='none'; if($('pfAvatarDel')) $('pfAvatarDel').style.display=''; }
else { if($('pfAvatarImg')) $('pfAvatarImg').style.display='none'; if($('pfAvatarInit')) $('pfAvatarInit').style.display=''; if($('pfAvatarDel')) $('pfAvatarDel').style.display='none'; }
window._seeking=!!(pr.data&&pr.data.seeking_clan); if($('btnSeek')) $('btnSeek').textContent = window._seeking?'🔴 إيقاف البحث عن كلان':'🔎 فعّل البحث عن كلان';
if($('pfTitle')) $('pfTitle').value=(pr.data&&pr.data.title)||'';
if($('pfBio')) $('pfBio').value=(pr.data&&pr.data.bio)||'';
if($('pfTitleCustom')) $('pfTitleCustom').value=(pr.data&&pr.data.title)||'';
sb.from('store_purchases').select('item_id,status,expires_at,store_items(perks)')
.eq('profile_id',user.id).eq('status','active').order('purchased_at',{ascending:false}).limit(1)
.then(function(spr){
var row=(spr.data&&spr.data[0])||null;
var active=row && (!row.expires_at || new Date(row.expires_at)>new Date());
var perks=active && row.store_items && row.store_items.perks || {};
var hasCustomTitle=!!perks.custom_rank_name;
if($('pfTitleCustomBox')) $('pfTitleCustomBox').hidden=!hasCustomTitle;
if($('pfTitleAdminNote')) $('pfTitleAdminNote').hidden=hasCustomTitle;
});
var isLinked=!!(pr.data&&pr.data.discord_id);
if($('btnLinkDiscord')) $('btnLinkDiscord').style.display = isLinked?'none':'';
if($('dcLinkP')) $('dcLinkP').innerHTML = isLinked
? '✅ حسابك مربوط بـDiscord'+(pr.data.discord_name?(' — '+esc(pr.data.discord_name)):'')+' أوامر البوت جاهزة: /تحدي · /فردي · /كلاني'
: 'اربط حسابك بـDiscord لتستخدم أوامر البوت (تحديات · بلاغات · رتبة كلانك تجيك تلقائياً).';
});
/* المؤرشف محفوظ باسمه لكنه لا يُقيّده — يقدر ينضم لكلان آخر */
/* ═══ من يقودُ كلاناً؟ ═══
كان يُقرأُ من `created_by` و`vice_id` وحدَهما، فالمنسّقُ لا كلانَ
له في نظرِ الواجهةِ وإن كانت القاعدةُ تعرفُه. و`my_lead_clan`
تجمعُ الأربعةَ (منشئ · رئيس · نائب · منسّق) وتستثني المؤرشف. */
sb.rpc('my_lead_clan',{p_user:user.id}).then(function(lr){
var cid=(lr&&lr.data)||null;
if(!cid){
window._myClanId=null; window._myClanTag=null;
if(window.renderClansCta) window.renderClansCta();
if(window.applyClanContext) window.applyClanContext();
return;
}
return sb.from('clans').select('id,tag').eq('id',cid).limit(1).then(function(cr){
if(cr&&cr.error){ if(window.applyClanContext) window.applyClanContext(); return; }
var clan=cr.data&&cr.data[0]; window._myClanId=clan?clan.id:null; window._myClanTag=clan?String(clan.tag):'';
if(false && $('myClanBtn')){
if(clan){ $('myClanH').textContent='🛡️ كلانك: CLAN '+String(clan.tag); $('myClanP').textContent='أدِر أعضاءك، النائب، الوصف واللوقو.'; $('myClanBtn').textContent='⚙️ إدارة الكلان'; $('myClanBtn').setAttribute('onclick',"go('manageclan')"); }
}
if(window.renderClansCta) window.renderClansCta();
if(window.applyClanContext) window.applyClanContext();
});
},function(){ if(window.applyClanContext) window.applyClanContext(); });
sb.from('clan_members').select('clan_id,role,clans(tag)').eq('profile_id',user.id).limit(1).then(function(mr){
if(mr&&mr.error){ if(window.applyClanContext) window.applyClanContext(); return; }
var m=mr.data&&mr.data[0]; window._myMemberClanId=m?m.clan_id:null; window._myMemberRole=m?m.role:null; window._myMemberClanTag=(m&&m.clans&&String(m.clans.tag))||'';
if(window.renderClansCta) window.renderClansCta();
if(window.applyClanContext) window.applyClanContext();
},function(){ if(window.applyClanContext) window.applyClanContext(); });
}
})();
}
/* حارس: لو ظهر «ضيف» ونحن مسجّلون فعلاً، أعد بناء الهوية */
setInterval(function(){
if(document.hidden) return;
var cn=document.getElementById('chipName');
if(window._uid && cn && cn.textContent.trim()==='ضيف') refreshAuth();
}, 20000);
var authMode='signup';
function setAuthMode(m){
authMode=m; var signup=(m==='signup');
$('authH').textContent = signup?'تسجيل جديد':'تسجيل دخول';
$('authP').textContent = signup?'أنشئ حسابك للانضمام للكلانات والتحديات':'ادخل باسم المستخدم أو بريدك وكلمة المرور';
$('acUser').hidden = false;
$('acEmail').hidden = !signup;
/* الغلاف هو ما يُخفى — لو أخفينا الحقل وحده بقي الغلاف بارتفاع صفر
وطفت شارة أزرار السوني فوق حقل كلمة المرور. */
if($('acPsnWrap')) $('acPsnWrap').hidden = !signup;
if($('acPsn')) $('acPsn').hidden = !signup;
if($('acPsnNote')) $('acPsnNote').hidden = !signup;
$('acUser').placeholder = signup ? 'اسم المستخدم (فريد لا يتكرّر)' : 'اسم المستخدم أو البريد';
$('btnAuth').textContent = signup?'✅ تسجيل جديد':'دخول';
$('discordWrap').hidden = false;
if($('forgotPw')) $('forgotPw').hidden = signup;
$('authSwitch').textContent = signup?'عندك حساب؟ تسجيل دخول':'جديد؟ أنشئ حساب';
$('authMsg').textContent='';
}
if($('authSwitch')) $('authSwitch').addEventListener('click', function(){ setAuthMode(authMode==='signup'?'login':'signup'); });
if($('btnAuth')) $('btnAuth').addEventListener('click', function(){
var u=($('acUser').value||'').trim(), p=$('acPass').value, e=($('acEmail').value||'').trim();
var psn=window.fixPsn(($('acPsn')||{}).value);
if($('acPsn')) $('acPsn').value=psn; /* يرى ما صُحّح قبل أن يُرفض */
if(authMode==='signup'){
var msg=$('authMsg'); msg.style.color='var(--gold)';
if(!u){ msg.textContent='اكتب اسم المستخدم'; return; }
if(!e){ msg.textContent='اكتب البريد (يلزم لاستعادة كلمة المرور)'; return; }
/* آيدي السوني هوية ثابتة — نتحقّق منه قبل إنشاء الحساب لا بعده */
if(!psn){ msg.style.color='var(--red)'; msg.textContent='🔑 آيدي السوني إجباري — هو هويتك بالمنصة'; if($('acPsn')) $('acPsn').focus(); return; }
if(psn.length<3||psn.length>24){ msg.style.color='var(--red)'; msg.textContent='⚠️ آيدي السوني من 3 إلى 24 حرفاً'; return; }
if(!/^[A-Za-z0-9_\-]+$/.test(psn)){ msg.style.color='var(--red)'; msg.textContent='⚠️ آيدي السوني: حروف وأرقام و _ - فقط'; return; }
if(p.length<6){ msg.textContent='كلمة المرور 6 أحرف على الأقل'; return; }
window.psnConfirm(psn, doSignup);
return;
function doSignup(){
msg.textContent='...التحقق من الاسم والآيدي';
/* اسم المستخدم هوية فريدة — نفحصه قبل إنشاء الحساب */
sb.rpc('username_available',{p_name:u}).then(function(ua){
if(ua&&ua.error){ msg.style.color='var(--red)'; msg.textContent=errAr(ua.error.message); return; }
if(ua&&ua.data===false){ msg.style.color='var(--red)'; msg.textContent='⚠️ اسم المستخدم «'+u+'» محجوز — اختر غيره'; if($('acUser')) $('acUser').focus(); return; }
return sb.rpc('psn_available',{p_psn:psn}).then(function(av){
if(av&&av.error){ msg.style.color='var(--red)'; msg.textContent=errAr(av.error.message); return; }
if(av&&av.data===false){ msg.style.color='var(--red)'; msg.textContent='⚠️ آيدي السوني «'+psn+'» مسجّل لعضو آخر — لو هو حقك راجع الإدارة'; return; }
msg.style.color='var(--gold)'; msg.textContent='...جاري التسجيل';
sb.auth.signUp({email:e,password:p,options:{data:{username:u, psn_id:psn}}}).then(function(s){
if(s.error){
msg.style.color='var(--red)';
if(/already registered|already exists/i.test(s.error.message||'')){ msg.textContent='البريد مسجّل مسبقاً — سجّل دخول.'; setAuthMode('login'); }
else msg.textContent=errAr(s.error.message);
return;
}
/* نثبّت الآيدي فور إنشاء الحساب (الدالة ترفض تغييره لاحقاً) */
sb.rpc('set_my_psn',{p_psn:psn}).then(function(){
msg.style.color='var(--green,#3ddc84)'; msg.textContent='✅ تم إنشاء حسابك!'; refreshAuth();
},function(){ msg.textContent='✅ تم إنشاء حسابك — احفظ آيدي السوني من «حسابي»'; refreshAuth(); });
});
});
},function(){ msg.style.color='var(--red)'; msg.textContent='تعذّر الاتصال'; });
}
} else {
if(!u||!p){ $('authMsg').textContent='اكتب اسم المستخدم أو البريد وكلمة المرور'; return; }
var am=$('authMsg');
window._authBusy=true; /* لا تُعاد الصفحة ودخولُه جارٍ */
am.style.color='var(--gold)'; am.textContent='...جاري الدخول';
/* ═══ لا انتظارَ بلا نهاية ═══
كان المسار بلا مهلة: إن تعثّرت الشبكة أو تأخّرت الخدمة، بقيت
«جاري الدخول» أبداً — ولا يعرف صاحبها أعطلٌ هو أم بطء، ولا
يُقال له أعِد المحاولة. كل خطوةٍ الآن لها سقفٌ زمني ورسالةٌ
تقول ما حدث، والزرّ يعود ليُضغط من جديد. */
var cap=function(pr, ms, what){
return Promise.race([
Promise.resolve(pr),
new Promise(function(_,rej){ setTimeout(function(){ rej(new Error('TIMEOUT:'+what)); }, ms); })
]);
};
var fail=function(t){ window._authBusy=false; am.style.color='var(--red)'; am.textContent=t; };
var slow=setTimeout(function(){
if(/جاري الدخول/.test(am.textContent)) am.textContent='...الخدمة بطيئة الآن — ننتظر قليلاً';
}, 4000);
cap(sb.rpc('email_for_login',{p_who:u}), 15000, 'name').then(function(r){
if(r&&r.error) throw new Error(r.error.message);
var email=r&&r.data;
if(!email){ clearTimeout(slow); fail('ما لقينا هذا الحساب — تأكّد من الاسم أو البريد'); return null; }
return cap(sb.auth.signInWithPassword({email:email,password:p}), 20000, 'auth');
}).then(function(r2){
clearTimeout(slow);
if(!r2) return;
if(r2.error){
fail(/invalid login/i.test(r2.error.message||'') ? 'اسم المستخدم أو كلمة المرور خطأ' : errAr(r2.error.message));
return;
}
window._authBusy=false; am.style.color=''; am.textContent=''; refreshAuth();
}).catch(function(e){
clearTimeout(slow);
var m=String((e&&e.message)||e);
if(/^TIMEOUT:/.test(m)){
fail('⏱️ الخدمة لم تردّ خلال المهلة — اضغط «دخول» مرّةً أخرى.'
+ (/:auth$/.test(m) ? ' (تعثّر التحقّق من كلمة المرور)' : ' (تعثّر التعرّف على الاسم)'));
} else if(/Failed to fetch|NetworkError|Load failed/i.test(m)){
fail('📶 انقطع الاتصال — تأكّد من الشبكة وأعد المحاولة.');
} else {
fail(errAr(m));
}
});
}
});
/* ═══ تأكيد آيدي السوني ═══ نافذة وقورة بدل confirm المتصفح —
الآيدي هوية ثابتة، فنعرضه كبيراً ليتفحّصه حرفاً حرفاً قبل الالتزام. */
window.psnConfirm=function(psn, onOk){
var old=document.getElementById('psnBack'); if(old) old.remove();
var b=document.createElement('div'); b.id='psnBack';
b.style.cssText='position:fixed;inset:0;z-index:96;background:rgba(2,6,12,.72);backdrop-filter:blur(3px);display:grid;place-items:center;padding:22px';
b.innerHTML=
'
'+
'
△○✕□
'+
'
تأكيد آيدي السوني
'+
'
'+esc(psn)+'
'+
'
تأكد من PSN ID أنه صحيح لأنه غير قابل للتعديل
'+
'
'+
''+
''+
'
'+
'
'+
'';
document.body.appendChild(b);
function close(){ b.remove(); }
b.addEventListener('click',function(e){ if(e.target===b) close(); });
document.getElementById('psnEdit').onclick=function(){ close(); if($('acPsn')) $('acPsn').focus(); };
document.getElementById('psnOk').onclick=function(){ close(); if(onOk) onOk(); };
};
/* ═══ الدخولُ بآبل — شرطُ المتجرِ 4.8 ═══
لا حيلةَ فيه ولا تحويلاتٍ يدويّة: سوبابيس تتولّى الجولةَ كلَّها.
وما دامَ المزوّدُ غيرَ مفعَّلٍ في لوحةِ سوبابيس يردُّ خطأً معروفاً،
فيُترجَمُ إلى عربيٍّ مفهومٍ بدلَ أن يُعرَضَ خاماً. */
window.appleAuth=function(msgEl){
var setMsg=function(t){ var e=$(msgEl); if(e) e.textContent=t; };
/* ═══ داخلَ تطبيقِ iOS: الورقةُ الأصليّةُ لا جولةُ متصفّح ═══
آبل ترفضُ إتمامَ الدخولِ في WebView مضمَّن، والقشرةُ تعملُ على
capacitor://localhost فلا عنوانَ رجوعٍ لها أصلاً. فالمسارُ الأصليُّ
هو الوحيدُ الذي يعمل: الإضافةُ تُعيدُ identityToken، وسوبابيس
يتحقّقُ منه بمفاتيحِ آبلَ العامّة. والحارسُ يفحصُ وجودَ الإضافةِ
نفسِها لا المنصّةَ وحدَها — قشرةٌ بُنيت قبلَ تركيبِها تسقطُ إلى
مسارِ الويبِ بدلَ أن ترميَ استثناءً في وجهِ العضو. */
var _cap = window.Capacitor;
var _swa = _cap && _cap.isNativePlatform && _cap.isNativePlatform()
&& _cap.Plugins && _cap.Plugins.SignInWithApple;
if(_swa){
setMsg('...فتح Apple');
_swa.authorize({ clientId:'com.codar.app',
redirectURI:'https://cod-ar.com/', scopes:'email name' })
.then(function(res){
var tok = res && res.response && res.response.identityToken;
if(!tok){ setMsg('لم تصل هويّة Apple — أعد المحاولة'); return; }
return sb.auth.signInWithIdToken({provider:'apple', token:tok})
.then(function(r){
if(r && r.error){ setMsg(errAr(r.error.message)); return; }
setMsg(''); if(window.refreshAuth) refreshAuth();
});
})
.catch(function(e){
var m = String((e && (e.message||e.errorMessage)) || e);
/* 1001 = ألغى العضوُ الورقةَ بنفسِه — لا رسالةَ خطأٍ على إلغاءٍ مقصود */
setMsg(/cancel|1001/i.test(m) ? '' : 'تعذّر الدخول بـApple — أعد المحاولة');
});
return;
}
setMsg('...التحويل إلى Apple');
sb.auth.signInWithOAuth({provider:'apple', options:{redirectTo: location.origin+'/'}})
.then(function(r){
if(r&&r.error){
setMsg(/not enabled|unsupported|provider/i.test(r.error.message||'')
? 'دخول Apple قيد التفعيل — استخدم البريد أو Discord الآن'
: errAr(r.error.message));
}
}, function(){ setMsg('تعذّر فتح صفحة Apple — تحقّق من اتصالك'); });
};
/* الدخول بديسكورد — يفتح تطبيق Discord بالجوال لو منزّل بدل صفحة الويب */
window.discordAuth=function(msgEl){
var setMsg=function(t){ var e=$(msgEl); if(e) e.textContent=t; };
setMsg('...التحويل إلى Discord');
var opts={provider:'discord', options:{
redirectTo: location.origin+'/', scopes:'identify email guilds.join', skipBrowserRedirect:true }};
sb.auth.signInWithOAuth(opts).then(function(r){
if(r&&r.error){
setMsg(/not enabled|unsupported|provider/i.test(r.error.message||'')
? 'دخول Discord قيد التفعيل — استخدم البريد الآن' : (errAr(r.error.message)));
return;
}
var url=r&&r.data&&r.data.url;
if(!url){ setMsg('تعذّر إنشاء رابط الدخول'); return; }
try{ localStorage.setItem('cx_dcPending', String(Date.now())); }catch(e){}
setMsg('📱 جاري فتح ديسكورد...');
var ua=navigator.userAgent||'';
var mobile=/iphone|ipad|ipod|android/i.test(ua);
var iOS=/iphone|ipad|ipod/i.test(ua);
var inApp=(window.matchMedia && window.matchMedia('(display-mode: standalone)').matches)
|| window.navigator.standalone===true;
/* ═══ أين يُفتح تطبيق ديسكورد وأين لا يُفتح؟ ═══
بعد الموافقة، ديسكورد يفتح رابط العودة في المتصفح الافتراضي لا في
نافذتنا. فالسؤال: هل يرى تطبيقنا الجلسة التي كُتبت هناك؟
· أندرويد: التطبيق المثبّت يعمل داخل كروم ويتشارك تخزينه — فيراها ✅
· آيفون مثبّت: تخزين التطبيق معزول عن سفاري تماماً — تضيع الجلسة ❌
فنُبقيه بنفس النافذة، ويرجع إلينا الدخول سليماً.
· أي متصفح جوال: نفس النافذة ونفس التخزين ✅ */
var appOK = mobile && !(inApp && iOS);
if(!appOK){ location.href=url; return; }
var t=setTimeout(function(){ location.href=url; }, 4000);
fetch('/api/oauth-url?u='+encodeURIComponent(url))
.then(function(x){ return x.json(); })
.then(function(d){
clearTimeout(t);
if(!d||!d.url){ location.href=url; return; }
window.showDiscordChoice(msgEl, d.deep||d.url, d.url);
})
.catch(function(){ clearTimeout(t); location.href=url; });
});
};
/* بطاقة الاختيار — الروابط هنا حقيقية عشان iOS يفتح التطبيق */
window.showDiscordChoice=function(msgEl, deep, web){
var el=$(msgEl); if(!el){ location.href=deep; return; }
var esc2=function(s){ return String(s).replace(/"/g,'"'); };
el.innerHTML=
'
'+
'
📱 عندك تطبيق ديسكورد؟ اضغط الأزرق — يفتح التطبيق وتوافق بضغطة، بلا ما تكتب إيميلك ولا كلمة مرورك.
';
var a=document.getElementById('dcApp');
if(a) a.addEventListener('click',function(){
// لو التطبيق مو منزّل ما ينفتح شي — نرجع للمتصفح تلقائياً
var back=setTimeout(function(){ if(!document.hidden) location.href=web; }, 2200);
document.addEventListener('visibilitychange',function(){ if(document.hidden) clearTimeout(back); },{once:true});
});
};
/* استئناف الدخول لو رجع المستخدم من تطبيق ديسكورد بلا جلسة */
(function(){
var tries=0;
function resume(){
var pend=0; try{ pend=parseInt(localStorage.getItem('cx_dcPending')||'0',10); }catch(e){}
if(!pend || (Date.now()-pend) > 10*60*1000) return; // انتهت المهلة
sb.auth.getSession().then(function(r){
var s=r&&r.data&&r.data.session;
if(s){ try{ localStorage.removeItem('cx_dcPending'); }catch(e){} if(window.refreshNow) window.refreshNow(); return; }
if(++tries<4){ setTimeout(resume, 1500); return; }
// ما وصلت الجلسة — نعرض زر استئناف واضح بدل ما يحتار
var el=document.getElementById('authMsg');
if(el && !document.getElementById('dcResume')){
el.innerHTML='
'+
'⏳ ما اكتمل الدخول؟ لو وافقت بتطبيق ديسكورد ورجعت هنا بدون تسجيل، اضغط:'+
''+
'
';
}
});
}
document.addEventListener('visibilitychange', function(){ if(!document.hidden){ tries=0; resume(); } });
window.addEventListener('pageshow', function(){ tries=0; resume(); });
setTimeout(resume, 2500);
})();
if($('btnApple')) $('btnApple').addEventListener('click', function(){ window.appleAuth('authMsg'); });
if($('btnDiscord')) $('btnDiscord').addEventListener('click', function(){ window.discordAuth('authMsg'); });
if($('forgotPw')) $('forgotPw').addEventListener('click', function(){
var e=prompt('اكتب بريدك المسجّل لإرسال رابط استعادة كلمة المرور:');
if(!e){ return; }
$('authMsg').textContent='...إرسال رابط الاستعادة';
sb.auth.resetPasswordForEmail(e.trim(), {redirectTo: location.origin+'/'}).then(function(r){
$('authMsg').textContent = (r&&r.error)?(errAr(r.error.message)):'📧 أرسلنا رابط إعادة التعيين على بريدك — افتحه وحدّث كلمتك.';
});
});
/* الجلسة تُستعاد من التخزين بعد أول فحص، فتبقى الترويسة «ضيف»
بينما بقية الصفحات تعرف صاحبها. نعيد بناء الهوية مع كل تغيّر. */
/* ═══ بصمةُ الجهاز — الحسابُ يتبدّلُ والجهازُ لا يتبدّل ═══
الآيبي يُغيَّرُ ببروكسي في ثانية، **والفزعةُ تفعلُها.** لكنَّ رسمَ اللوحةِ
ورقاقةَ الرسومِ والشاشةَ والمنطقةَ الزمنيّةَ لا تتبدّلُ بضغطةِ زرّ — فتجمعُ
بصمةً تربطُ حسابَينِ على جهازٍ واحد.
**وهي دليلُ اشتباهٍ لا دليلُ إدانة:** أخوانِ في بيتٍ واحدٍ يعطيانِ البصمةَ
نفسَها. تُعرَضُ على الطاقمِ ليبتَّ، ولا يُعاقَبُ بها آليّاً أبداً.
وثلاثةُ قيودٍ في تنفيذِها:
**لا تُسجَّلُ في وضعِ الانتحال** — وإلّا نُسِبَ جهازُ المالكِ لكلِّ من دخلَ
بحسابِه فامتلأَ الرادارُ بمطابقاتٍ كاذبةٍ من صنعِنا.
**مرّةً واحدةً في الجلسة** — لا نداءَ مع كلِّ تجديدِ رمز.
**والكتابةُ بدالّةٍ تكتبُ باسمِ الجالسِ لا بما يُرسِلُه** — فلا يُحقَنُ
باسمِ غيرِه. */
window.codarTouchDevice = function(){
try{
if(window._fpDone) return;
try{ if(sessionStorage.getItem('codar_imp_on')==='1' ||
sessionStorage.getItem('codar_imp_mode')) return; }catch(e){}
if(!window.crypto || !crypto.subtle || !window.sb) return;
window._fpDone = true;
var p = [];
try{
var c = document.createElement('canvas'); c.width=200; c.height=42;
var g = c.getContext('2d');
g.textBaseline='top'; g.font='15px Arial';
g.fillStyle='#f60'; g.fillRect(0,0,72,18);
g.fillStyle='#069'; g.fillText('COD-AR 2026', 2, 2);
g.fillStyle='rgba(102,204,0,.72)'; g.fillText('COD-AR 2026', 4, 9);
p.push(c.toDataURL().slice(-180));
}catch(e){ p.push('nc'); }
try{
var gc = document.createElement('canvas').getContext('webgl');
var dx = gc && gc.getExtension('WEBGL_debug_renderer_info');
p.push(dx ? String(gc.getParameter(dx.UNMASKED_RENDERER_WEBGL)) : 'ng');
}catch(e){ p.push('ng'); }
p.push(navigator.hardwareConcurrency||0, navigator.deviceMemory||0,
screen.width+'x'+screen.height+'x'+(screen.colorDepth||0),
window.devicePixelRatio||0,
(Intl.DateTimeFormat().resolvedOptions().timeZone)||'',
navigator.language||'', navigator.platform||'',
navigator.maxTouchPoints||0);
crypto.subtle.digest('SHA-256', new TextEncoder().encode(p.join('|')))
.then(function(h){
var fp = Array.prototype.map.call(new Uint8Array(h), function(b){
return ('0'+b.toString(16)).slice(-2); }).join('').slice(0,32);
window.sb.rpc('touch_device', { p_fp: fp,
p_ua: String(navigator.userAgent||'').slice(0,300) });
}, function(){});
}catch(e){}
};
/* ═══ من جاءَ من صفحةٍ يرجعُ إليها ═══
أدواتُنا خارجَ التطبيقِ (ـ/zoom مثلاً) تطلبُ تسجيلَ الدخول، فيدخلُ
صاحبُها **فيجدُ نفسَه في الرئيسةِ** ولا يعرفُ كيف يعود. فتحملُ وصلةُ
الدخولِ `ret=` باسمِ الوجهة، ونردُّه إليها أوّلَ ما تثبتُ جلستُه.
والوجهةُ من **قائمةِ أسماءٍ مغلقة** لا من النصِّ كما جاء: قبولُ مسارٍ
حرٍّ من عنوانِ الصفحةِ بابُ تحويلٍ مفتوحٍ يُساقُ به الناسُ إلى مواقعَ
أخرى بوصلةٍ تبدو وصلتَنا.
والمفتاحُ يُمحى **قبلَ** التحويلِ لا بعدَه: لو بقيَ لدارت الصفحةُ على
نفسِها كلَّما ثبتت الجلسةُ من جديد. ويعيشُ في `sessionStorage` لأنَّ
الدخولَ بديسكوردَ يخرجُ من موقعِنا ويعودُ — فيضيعُ ما في العنوانِ ويبقى
ما في المخزنِ ما دامَ التبويبُ مفتوحاً. */
(function(){
var DEST = { zoom: '/zoom' }; /* أسماءٌ مسموحةٌ لا مساراتٌ حرّة */
var KEY = 'codar_ret';
try {
var q = new URLSearchParams(location.search).get('ret');
if (q && DEST[q]) sessionStorage.setItem(KEY, DEST[q]);
} catch(e){}
function goBack(){
var to;
try { to = sessionStorage.getItem(KEY); } catch(e){ return; }
if (!to) return;
try { sessionStorage.removeItem(KEY); } catch(e){}
if (location.pathname === to) return; /* نحن فيها أصلاً */
location.replace(to);
}
try {
sb.auth.onAuthStateChange(function(ev, sess){
if (sess && sess.user && (ev==='SIGNED_IN' || ev==='INITIAL_SESSION')) setTimeout(goBack, 260);
});
sb.auth.getSession().then(function(r){
if (r && r.data && r.data.session) setTimeout(goBack, 260);
}, function(){});
} catch(e){}
})();
sb.auth.onAuthStateChange(function(ev, sess){
if(window.codarBootStep && !window._bootIdent && !(sess && sess.user)) window.codarBootStep('auth');
if(ev==='SIGNED_IN' || ev==='TOKEN_REFRESHED' || ev==='INITIAL_SESSION' || ev==='USER_UPDATED'){
var uid=sess&&sess.user&&sess.user.id;
if(uid && uid!==window._uid) refreshAuth();
else if(uid && !document.getElementById('chipName')) refreshAuth();
else if(uid){
// نفس المستخدم — نتأكد أن الشريحة ما زالت تعرض «ضيف»
var cn=document.getElementById('chipName');
if(cn && cn.textContent.trim()==='ضيف') refreshAuth();
}
}
/* شبكةُ أمانٍ ثانية: أوّل جلسةٍ تصل بعد الإقلاع قد تكون بعد قراءة
الصلاحيات — فنُعيد قراءتها معها ولو لم تتبدّل الهوية. */
if(ev==='INITIAL_SESSION' || ev==='SIGNED_IN'){
if(sess&&sess.user){ if(window.codarTouchDevice) window.codarTouchDevice();
clearTimeout(window._apT2);
window._apT2=setTimeout(function(){ if(window.applyPerms) window.applyPerms(); }, 700); }
}
if(ev==='SIGNED_OUT'){ window._uid=null; if(window.renderIdentity) window.renderIdentity(null); }
if(ev==='PASSWORD_RECOVERY'){
var np=prompt('اكتب كلمة المرور الجديدة (6 أحرف على الأقل):');
if(np && np.length>=6){
sb.auth.updateUser({password:np}).then(function(r){ alert((r&&r.error)?(errAr(r.error.message)):'✅ تم تغيير كلمة المرور — استخدمها بالدخول.'); refreshAuth(); });
} else if(np!==null){ alert('كلمة المرور قصيرة (6 أحرف على الأقل)'); }
}
});
setAuthMode('signup');
if($('btnLogout')) $('btnLogout').addEventListener('click', function(){ sb.auth.signOut().then(refreshAuth); });
window.applyClantThemeColor=function(){
var hex=document.getElementById('mgColorHex');
var pick=document.getElementById('mgColorPick');
var color=(hex&&hex.value.trim())||(pick&&pick.value)||'#FFD700';
if(!color.match(/^#[0-9a-fA-F]{3,6}$/)){
alert('لون غير صحيح — استخدم صيغة مثل #FFD700');
return;
}
if(!window._myClanId){ alert('ما عندك كلان'); return; }
sb.rpc('clan_set_theme_color',{p_clan_id:window._myClanId, p_color:color}).then(function(r){
if(r.error||!r.data||!r.data.ok){ alert('تعذّر: '+(r.error?r.error.message:(r.data&&r.data.why)||'خطأ')); return; }
toast('✅ تم تغيير لون الكلان');
});
};
document.getElementById('mgColorPick')&&document.getElementById('mgColorPick').addEventListener('input',function(){
var hex=document.getElementById('mgColorHex'); if(hex) hex.value=this.value;
});
/* ═══ إيصال الدفع ═══ */
window.openReceipt=function(pid){
if(!pid){ toast('لا توجد عملية'); return; }
sb.rpc('get_receipt',{p_purchase_id:pid}).then(function(r){
var d=(r&&r.data)||{};
if(r.error||!d.ok){
toast(d.why==='unpaid'?'لا إيصال لعملية غير مكتملة'
: d.why==='not_yours'?'هذا الإيصال ليس لك'
: 'تعذّر جلب الإيصال');
return;
}
var f=function(x){ return x?new Date(x).toLocaleDateString('ar-SA'):'—'; };
var ft=function(x){ return x?new Date(x).toLocaleString('ar-SA'):'—'; };
var row=function(k,v){
return '
شكراً لدعمك منصة COD·AR '+
'لأي استفسار عن هذا الإيصال، افتح تذكرة من «التواصل مع الإدارة»
'+
''+
'
';
var w=window.open('','_blank');
if(!w){ toast('اسمح بالنوافذ المنبثقة لعرض الإيصال'); return; }
w.document.write(html); w.document.close();
});
};
window.askRefund=function(){
var pid=window._mySubPurchaseId;
if(!pid){ toast('لا يوجد اشتراك قابل للاسترداد'); return; }
var why=prompt('اكتب سبب طلب الاسترداد:\n\nالاسترداد متاح خلال 3 أيام من الشراء، وتراجعه الإدارة عبر تذكرة.','');
if(why===null) return;
why=String(why).trim();
if(why.length<10){ toast('اكتب سبباً واضحاً (10 أحرف على الأقل)'); return; }
sb.rpc('request_refund',{p_purchase_id:pid, p_reason:why}).then(function(r){
var d=r&&r.data;
if(r.error||!d||!d.ok){
var w=d&&d.why;
if(w==='too_late') toast('⏰ انتهت مهلة الاسترداد (3 أيام)');
else if(w==='already_requested') toast('عندك طلب استرداد قيد المراجعة');
else if(w==='not_refundable') toast('هذا الاشتراك غير قابل للاسترداد');
else toast('تعذّر إرسال الطلب');
return;
}
toast('✅ أُرسل طلب الاسترداد — تابعه من «التواصل مع الإدارة»');
});
};
window.loadMySubscription=function(){
if(!window._uid) return;
var box=document.getElementById('mySubBox');
var none=document.getElementById('noSubBox');
if(!box||!none) return;
sb.rpc('get_my_subscriptions').then(function(r){
var L=(r&&r.data)||[];
if(!L.length){ box.hidden=true; none.hidden=false; return; }
none.hidden=true; box.hidden=false;
window._mySubs=L;
window._mySubPurchaseId=L[0].purchase_id;
box.innerHTML=L.map(function(x){
var dia=(x.item_id||'').indexOf('diamond')>-1;
var tc=dia?'#b9f2ff':'#FFD700';
var bc=dia?'rgba(185,242,255,.35)':'rgba(255,215,0,.35)';
var bg=dia?'rgba(185,242,255,.08)':'rgba(255,215,0,.08)';
var lbl=dia?'DIAMOND':'GOLD';
var exp='';
if(x.expires_at){
var dt=new Date(x.expires_at);
var dl=Math.ceil((dt-Date.now())/86400000);
exp='ينتهي '+dt.toLocaleDateString('ar-SA')+' ('+(dl>0?dl+' يوم':'اليوم')+')';
} else exp='لا ينتهي';
var canRefund=false;
if(x.purchased_at) canRefund=((Date.now()-new Date(x.purchased_at).getTime())/86400000)<=3;
return '
';
return h;
}
window.loadClanDetail=function(clanId){
if(!clanId) return;
sb.from('clans').select('*').eq('id',clanId).single().then(function(r){
var c=r.data; if(!c) return;
if($('cdInit')) $('cdInit').textContent=(c.tag||'?').slice(0,4);
/* ═══ الترويسةُ هويّةُ الكلانِ لا رتبتُه ═══
وضعتُ الشارةَ هنا فحجبتْ لوقو الكلانِ — والرتبةُ ظاهرةٌ في البطاقةِ
تحتَها مباشرةً فلا تحتاجُ موضعَين. فرجعَتِ الهويّةُ إلى مكانِها،
واللوقو أولى من الحرفَين إن رفعَه أهلُه، والرتبةُ شارةٌ صغيرةٌ في
الزاويةِ تُعلِّمُ ولا تُزاحِم. */
(function(){
/* ═══ الحاويةُ تبقى ومحتواها يتبدّل ═══
كان يُستبدَلُ العنصرُ نفسُه بـ`outerHTML`، فيختفي `cdInit`؛
وفي الزيارةِ الثانيةِ لا يُوجَدُ فيخرجُ قبلَ الرسم — فيبقى
لوقو الكلانِ الأوّل. فصارتْ حاويةٌ ثابتةٌ تُملأُ كلَّ مرّة. */
var hx=document.getElementById('cdFace'); if(!hx) return;
var wrap='
';
var face = c.logo_url
? ''
: '
'+esc((c.tag||'?').slice(0,4))+'
';
hx.innerHTML = wrap + face +
/* ظلٌّ مزدوج: قريبٌ حادٌّ يفصلُها عن اللوقو، وبعيدٌ ناعمٌ يرفعُها فوقَه.
بلا هذا تُقرأُ الشارةُ جزءاً من اللوقو لا شيئاً موضوعاً عليه. */
'
';
if(window.RANKS_ON && window.tierBadge){
window.rankTiers(function(){
var pin=document.getElementById('cdRankPin');
if(pin) pin.innerHTML=window.tierBadge(c.points,44,{still:true});
});
}
})();
if($('cdName')) $('cdName').innerHTML='CLAN '+esc(c.tag)+' | COD-AR';
/* تطبيق لون الكلان المخصص — نُعيد الضبط أولاً */
var cdName=document.getElementById('cdName');
var cdRankPin=document.getElementById('cdRankPin');
if(cdName) cdName.style.color='';
if(cdRankPin) cdRankPin.style.filter='';
if(c.theme_color){
if(cdName) cdName.style.color=c.theme_color;
if(cdRankPin) cdRankPin.style.filter='drop-shadow(0 0 6px '+c.theme_color+')';
}
/* فحص هل رئيس الكلان يبث */
sb.from('clan_members').select('profile_id').eq('clan_id',clanId).in('role',['leader','vice']).then(function(mr){
var leaderIds=(mr.data||[]).map(function(m){return m.profile_id;});
if(!leaderIds.length) return;
sb.from('streamers').select('id,platform,platform_username,display_name,is_live,stream_title,viewer_count').in('profile_id',leaderIds).eq('is_live',true).then(function(sr){
var live=sr.data&&sr.data[0];
var liveTag=document.getElementById('cdLiveTag');
if(!live){if(liveTag)liveTag.remove(); return;}
var pn=live.platform==='kick'?'Kick':live.platform==='twitch'?'Twitch':'TikTok';
if(!liveTag){
liveTag=document.createElement('div');
liveTag.id='cdLiveTag';
liveTag.style.cssText='margin:8px 0;display:flex;align-items:center;gap:8px;flex-wrap:wrap';
var cdName=$('cdName');
if(cdName&&cdName.parentNode) cdName.parentNode.insertBefore(liveTag,cdName.nextSibling);
}
liveTag.innerHTML=''+
''+
'يبث الآن على '+pn+
(live.viewer_count?' · 👁 '+live.viewer_count:'')+
'';
});
});
var type=typeBadge(c.type);
var stt=c.status==='ratified'?'✔ معتمد':'بانتظار الترسيم';
var fire=(c.streak&&c.streak>=2)?'🔥 ستريك '+c.streak+'':'';
if($('cdBadges')) $('cdBadges').innerHTML=type+stt+fire;
sb.from('public_clan_warnings').select('level').eq('clan_id',c.id).limit(1).then(function(wr){
var w=(wr.data&&wr.data[0])||null;
if(!w || !$('cdBadges')) return;
var wLabel = w.level==='first'?'⚠️ إنذار أول':(w.level==='second'?'⚠️ إنذار ثانٍ':'🚨 إنذار نهائي');
var wColor = w.level==='final' ? 'background:rgba(255,80,70,.15);border:1px solid rgba(255,80,70,.5);color:#ff6b5e' : 'background:rgba(255,140,60,.15);border:1px solid rgba(255,140,60,.5);color:#ff8c3c';
$('cdBadges').innerHTML += ''+wLabel+'';
});
/* ═══ بطاقةُ الرتبةِ وكم بقيَ للقادمة ═══
الرقمُ وحدَه لا يُحرّك: «٣٧٣ نقطة» لا تقولُ شيئاً. أمّا «باقٍ ٢٧
نقطةً لمحترف — وفوزٌ واحدٌ يعطيكَ نحوَ ٥٠» فتقولُ للرئيسِ ما يفعلُه
الليلة. فالبطاقةُ تجمعُ الثلاثةَ: أينَ أنت، وكم بقي، وبمَ تقطعُه. */
(function(){
var box=$('cdRank'); if(!box || !window.RANKS_ON) return;
window.rankTiers(function(T){
if(!T.length){ box.innerHTML=''; return; }
var p=Math.max(0,c.points||0), cur=T[0], nxt=null;
for(var i=0;i=T[i].min_points) cur=T[i]; else { nxt=T[i]; break; } }
var body;
if(nxt){
var span=nxt.min_points-cur.min_points, done=p-cur.min_points;
var pct=Math.max(0,Math.min(100,Math.round(done/Math.max(span,1)*100)));
var left=nxt.min_points-p;
body='
'+
'باقٍ '+left.toLocaleString('en')+' نقطة لرتبة '+esc(nxt.name)+''+
''+pct+'%
';
/* ═══ آخر تحرير ═══
سطرٌ على كل صفحة كلان: متى مسّها أهلُها آخرَ مرّة — يستوي فيه
الوصفُ واللوقو والتاقُ ودخولُ عضوٍ وخروجه. والوقتُ بتوقيت
الرياض واثنتي عشرة ساعة كما يقرأ الناسُ لا كما تخزّن القاعدة. */
var ed=c.edited_at&&window.tDT?window.tDT(c.edited_at):'';
if(ed) h+='
'+
'✏️ آخر تحرير للصفحة: '+esc(ed)+''+
' · بتوقيت السعودية
'+
'🤝'+
/* صار الفوزُ غيرُ المصنَّفِ داخلاً في العدّادِ الأعلى، فلو بقيت
التسميةُ سجلّاً مستقلّاً قُرِئت سجلّاً ثانياً وجُمِعت مرّتين.
«منها» تقولُ إنّها بعضُ الأعلى لا شيءٌ سواه. */
'منها غير مصنّفةتُحسب في السجل ولا تُعطي نقاطاً ولا تصنيفاً'+
''+cw+' · '+cl+''+
'
'
: '';
if($('cdActions')){
var tg2=String(c.tag||'؟');
var mine=(window._uid&&(c.created_by===window._uid||c.vice_id===window._uid));
var inClan=!!(window._myClanId||window._myMemberClanId);
var open=(c.recruiting!==false);
var _actHTML = mine
? '
'+
''+
'
'
: (open && !inClan && window._uid && c.status==='ratified')
/* باب مفتوح وأنت حر: الدعوة واضحة بلا زحمة.
وإن كنتَ أرسلتَ طلباً فالزرّ يختفي حتى تمرّ ٢٤ ساعة. */
? (function(){
var w=window.joinWait?window.joinWait(c.id):0;
var head='
'+
'🚪'+
'هذا الكلان يستقبل أعضاء
';
var body = w>0
? '
📨 أرسلت طلب انضمام لهذا الكلان تقدر ترسل مرة ثانية بعد '+window.fmtWait(w)+'
'
: ''+
'
يوصل طلبك لرئيس الكلان — ويرد عليك بالتنبيهات
';
return '
'+head+body+'
';
})()
: (open && inClan)
? '
🚪 يستقبل أعضاء — واجب الخروج من كلانك أولاً للانضمام
⏳ الكلان بانتظار الترسيم — يفتح باب الانضمام بعد اعتماد الإدارة
'
: '';
/* ═══ cdTopAct — الفعلُ في الأعلى لا في القاع ═══
زرُّ الانضمامِ كان على ارتفاعِ ١١٠٠ بكسلٍ من رأسِ الصفحة، **بعدَ
الرتبةِ والأوسمةِ والإحصاءِ والتعريف** — ومن دخلَ ليطلبَ الانضمامَ
لا يريدُ قراءةَ كلِّ ذلك أوّلاً. فيصعدُ إلى شريطِ الرأسِ مقابلَ
زرِّ الرجوع: يُرى بلا تمرير، ويوفّرُ على الصفحةِ صندوقاً كاملاً.
**وما صعدَ لا يُكرَّرُ في الأسفل** — زرّانِ للفعلِ الواحدِ يُربكان
ويُطيلانِ ما أردنا تقصيرَه. أمّا حالُ المهلةِ فتبقى في الأسفلِ
لأنّها شرحٌ لا فعل، والشرحُ لا يُختصَرُ في رقاقة. */
var _top = $('cdTopAct');
var _w = window.joinWait ? window.joinWait(c.id) : 0;
var _canJoin = (open && !inClan && window._uid && c.status === 'ratified');
if(_top){
_top.innerHTML = mine
? ''
: (_canJoin && !_w)
? ''
: '';
}
$('cdActions').innerHTML = (mine || (_canJoin && !_w)) ? '' : _actHTML;
}
});
/* profile_id لازمٌ هنا: الاسم يصير باباً إلى ملفّه، وبدونه يظنّه
الحارسُ عضواً بلا حساب فيتركه نصّاً لا يستجيب للضغط. */
sb.from('clan_members').select('profile_id,role,member_name,psn,title,profiles(username,psn_id)').eq('clan_id',clanId).then(function(mr){
var order={leader:0,vice:1,member:2};
var rows=(mr.data||[]).slice().sort(function(a,b){ return (order[a.role]||2)-(order[b.role]||2); });
if($('cdMemCount')) $('cdMemCount').textContent=rows.length+' عضو';
if($('cdMembers')) $('cdMembers').innerHTML= rows.length? hierarchyHTML(rows) : '
لا أعضاء بعد
';
});
/* سجل مباريات الكلان — من السجل الدائم بالتاريخ والوقت */
sb.from('match_log').select('*').eq('kind','team')
.or('clan_a.eq.'+clanId+',clan_b.eq.'+clanId)
.order('verified_at',{ascending:false})
.limit(window._cdAllM===clanId?500:40).then(function(rr){
var all=rr.data||[];
var W=all.filter(function(x){return x.winner_clan===clanId;}).length, Lo=all.length-W;
var rk=all.filter(function(x){return x.ranked!==false;}).length;
var head = all.length
? '
';
}).join('');
box.innerHTML=h;
};
if(ids.length) sb.from('profiles').select('id,username').in('id',ids).then(function(pr){
var nm={}; (pr.data||[]).forEach(function(p){ nm[p.id]=p.username; }); draw(nm);
},function(){ draw({}); });
else draw({});
},function(){ box.innerHTML=''; if(sec) sec.hidden=true; });
};
window.createDuel=function(){
var msg=$('duelMsg'); if(!msg) return;
if(_blocked()){ msg.style.color='var(--red)'; msg.textContent='🚫 حسابك محظور'; return; }
window.authUser().then(function(res){ var u=res&&res.data&&res.data.user;
if(!u){ msg.style.color='var(--red)'; msg.textContent='سجّل دخولك أولاً'; return; }
sb.from('profiles').select('username,psn_id').eq('id',u.id).single().then(function(pr){
var myPsn=(pr.data&&pr.data.psn_id)||'', myName=(pr.data&&pr.data.username)||'';
if(!myPsn){ msg.style.color='var(--red)'; msg.innerHTML='⚠️ احفظ PSN ID في حسابك أول عشان يقدر الخصم يضيفك'; return; }
var oppName=(($('duelOpp')&&$('duelOpp').value)||'').trim();
var note=(($('duelNote')&&$('duelNote').value)||'').trim().slice(0,90);
var K=duKills();
var doInsert=function(oppId,oppUser,oppPsn){
sb.from('duels').insert({challenger_id:u.id, challenger_name:myName, challenger_psn:myPsn,
opponent_id:oppId||null, opponent_name:oppUser||null, opponent_psn:oppPsn||null,
status:oppId?'pending':'open', target_kills:K, note:note||null}).then(function(r){
if(r.error){ msg.style.color='var(--red)'; msg.textContent=errAr(r.error.message); return; }
msg.style.color='#3ddc84';
msg.textContent=oppId?'✅ وصله طلبك — ننتظر قبوله':'✅ طلب مفتوح — انتظر من يتطوّع';
if($('duelOpp')) $('duelOpp').value=''; if($('duelNote')) $('duelNote').value='';
if(oppId) sb.from('notifications').insert({user_id:oppId,title:'⚔️ تحدّاك لاعب 1v1',
body:myName+' يبي يواجهك — أول من يوصل '+K+' كيل يفوز'}).then(function(){},function(){});
window.loadSolo();
});
};
if(!oppName){ doInsert(null,null,null); return; }
sb.from('profiles').select('id,username,psn_id').ilike('username',oppName).limit(1).then(function(op){
var o=op.data&&op.data[0];
if(!o){ msg.style.color='var(--red)'; msg.textContent='ما لقيت لاعب باسم: '+oppName; return; }
if(o.id===u.id){ msg.style.color='var(--gold)'; msg.textContent='ما تقدر تتحدى نفسك 😄'; return; }
if(!o.psn_id){ msg.style.color='var(--gold)'; msg.textContent='هذا اللاعب ما حفظ PSN بعد'; return; }
doInsert(o.id,o.username,o.psn_id);
});
});
});
};
var duWhy={auth:'سجّل دخولك',not_found:'الطلب غير موجود',closed:'الطلب انسحب أو قُبل من غيرك',
own:'هذا طلبك أنت',banned:'🚫 حسابك محظور',no_psn:'احفظ PSN في حسابك أول',
already:'تقدّمت لهذا الطلب من قبل',forbidden:'ما تملك هذا الإجراء',done:'تمّ الردّ عليه',
no_duel:'المواجهة غير موجودة',
use_withdraw:'⚔️ المواجهة جارية — ما تُلغى، وثّق نتيجتها أو اضغط «انسحب من المواجهة» بغرفتها'};
function duRun(fn,args,okMsg){
sb.rpc(fn,args).then(function(r){
var d=r&&r.data;
if(r&&r.error){ toast(errAr(r.error.message)); return; }
if(d&&d.ok===false){ toast(duWhy[d.why]||'تعذّر الإجراء'); if(d.why==='no_psn') go('account'); return; }
toast(okMsg); window.loadSolo();
},function(){ toast('تعذّر الاتصال'); });
}
window.bidDuel=function(id){
if(_blocked()) return; if(!window._uid){ go('account'); return; }
duRun('bid_duel',{p_duel:parseInt(id,10),p_note:null},'🙋 تطوّعت — ننتظر اختياره');
};
window.acceptDuel=function(id){
if(_blocked()) return; if(!window._uid){ go('account'); return; }
duRun('accept_duel',{p_did:parseInt(id,10)},'✅ بدأت المواجهة — تبادلوا الـPSN');
};
window.cancelDuel=function(id){
if(!confirm('إلغاء هذه المواجهة؟')) return;
duRun('cancel_duel',{p_did:parseInt(id,10)},'أُلغيت المواجهة');
};
window.answerDuelBid=function(bid,ok){
duRun('answer_duel_bid',{p_bid:bid,p_accept:!!ok}, ok?'✅ بدأت المواجهة':'رفضت التقدّم');
};
window.pullDuelBid=function(bid){ duRun('pull_duel_bid',{p_bid:bid},'سحبت تقدّمك'); };
/* ═══════════ غرفة المواجهة ═══════════
مكانٌ خاصٌّ بالطرفين يتّفقان فيه على الموعد ويتبادلان الـPSN —
بدل ما يتراسلان خارج المنصة فتضيع الاتفاقات عند الخلاف. */
window.closeDuelRoom=function(){
if(window._dqCh){ try{ sb.removeChannel(window._dqCh); }catch(e){} window._dqCh=null; }
window._dqId=null;
};
/* ═══ رابط الغرفة ═══
يفتحها لمن يملك حقّ دخولها — والحقّ من سياسات القاعدة لا من الرابط.
فلو تسرّب لغير أهله لم يرَ شيئاً. */
window.roomLink=function(kind,id){
return location.origin+'/?s='+(kind==='dq'?'dq':'chchat')+'&focus='+encodeURIComponent(id);
};
document.addEventListener('click',function(e){
var b=e.target&&e.target.closest?e.target.closest('[data-roomlink]'):null;
if(!b) return;
e.preventDefault();
var p=String(b.getAttribute('data-roomlink')||'').split(':');
var url=window.roomLink(p[0], p.slice(1).join(':'));
var done=function(){ toast('🔗 نُسخ رابط الغرفة'); };
if(navigator.clipboard&&navigator.clipboard.writeText){
navigator.clipboard.writeText(url).then(done,function(){ prompt('انسخ الرابط:',url); });
} else prompt('انسخ الرابط:',url);
});
/* ═══ الدخول بالرابط ═══
الداخل من رابطٍ لا يملك ذاكرة الشاشة، فنجلب المواجهة له. بها يدخل
المشرف غرفةً لم يفتح صفحتها أصلاً. */
window.openDuelRoomById=function(id){
if(!window._uid){ go('account'); return; }
var d=(window._duRows||{})[id];
if(d) return window.openDuelRoom(id);
sb.from('duels').select('*').eq('id',parseInt(id,10)).single().then(function(r){
if(!r||!r.data){ toast('المواجهة غير موجودة'); return; }
window._duRows=window._duRows||{}; window._duRows[r.data.id]=r.data;
window.openDuelRoom(r.data.id);
},function(){ toast('تعذّر فتح المواجهة'); });
};
window.openDuelRoom=function(id){
var d=(window._duRows||{})[id]; if(!d){ return window.openDuelRoomById(id); }
if(!window._uid){ go('account'); return; }
window._dqId=parseInt(id,10); window._dqRow=d;
go('dq');
window.renderDuelRoom();
};
window.renderDuelRoom=function(){
var d=window._dqRow||{}, id=window._dqId; if(!id) return;
if($('dqTitle')) $('dqTitle').innerHTML='⚡ '+esc(d.challenger_name||'؟')+' ضد '+esc(d.opponent_name||'؟');
if($('dqSub')) $('dqSub').textContent='🎯 أول من يوصل '+(d.target_kills||duKills())+' كيل يفوز';
if($('dqPsn')) $('dqPsn').innerHTML=
'🎮 '+esc(d.challenger_name||'؟')+': '+esc(d.challenger_psn||'—')+' '+
'🎮 '+esc(d.opponent_name||'؟')+': '+esc(d.opponent_psn||'—')+'';
var live=(d.status==='accepted');
/* المشرف يأخذ رابط الغرفة ليعود إليها أو يمرّره لزميله */
var modLink = window._isStaff
? '' : '';
if($('dqActs')) $('dqActs').innerHTML = (live
? ''+
''+
''+
''
: '
المواجهة '+(d.status==='completed'?'انتهت':'أُلغيت')+' — الغرفة للقراءة فقط
')
+ modLink;
var bar=document.querySelector('#dq [id="dqInput"]');
if(bar) bar.disabled=!live;
['dqAttach','dqMic','dqSend'].forEach(function(x){ var e=$(x); if(e) e.disabled=!live; });
window.loadDuelChat();
/* بثٌّ حيّ: رسالة الطرف الآخر تظهر بلا تحديث */
if(window._dqCh){ try{ sb.removeChannel(window._dqCh); }catch(e){} }
window._dqCh=sb.channel('dq'+id)
.on('postgres_changes',{event:'INSERT',schema:'public',table:'duel_chat',filter:'duel_id=eq.'+id},
function(){ window.loadDuelChat(); }).subscribe();
};
window.loadDuelChat=function(){
var box=$('dqMsgs'), id=window._dqId; if(!box||!id) return;
sb.from('duel_chat').select('*').eq('duel_id',id).order('id',{ascending:true}).limit(200).then(function(r){
if(window._dqId!==id) return; /* غرفة أخرى فُتحت أثناء الانتظار */
if(r.error){ box.innerHTML='
'; return; }
var rows=r.data||[];
box.innerHTML = rows.length ? rows.map(function(m){
if(m.system){
/* لون الرسالة يقول حكمها — كما في غرفة الكلانات */
var K={ report:['rgba(255,204,0,.07)','rgba(255,204,0,.4)','var(--gold)'],
verdict_ok:['rgba(61,220,132,.08)','rgba(61,220,132,.45)','#3ddc84'],
verdict_no:['rgba(255,107,94,.08)','rgba(255,107,94,.45)','#ff8c7a'],
/* النقضُ والاسترجاعُ كهرمانيّانِ: ليسا قبولاً ولا رفضاً */
verdict_revert:['rgba(232,153,58,.09)','rgba(232,153,58,.5)','#f0a94a'],
restored:['rgba(232,153,58,.07)','rgba(232,153,58,.32)','#e8993a'] };
var k=K[m.kind]||['rgba(56,182,255,.07)','rgba(56,182,255,.25)','var(--blue2)'];
var b0=String(m.body||''), nl=b0.indexOf('\n');
var hd=(nl>0)?b0.slice(0,nl):b0, rest=(nl>0)?b0.slice(nl+1):'';
return '
'+
'
'+esc(hd)+'
'+
(rest?'
'+esc(rest)+'
':'')+
'
'+tHM(m.created_at)+'
';
}
var own=(m.user_id===window._uid);
var att='';
if(m.media_url){
var mu=String(m.media_url).replace(/"/g,'"');
att = (m.kind==='audio')
? ''
: window.isVid(mu)
? ''+
''+
'▶'
: '';
}
var f=window.fxOf(m.user_id), fb=window.fxBubble(f);
var nm=f?window.fxName(f,m.username||'لاعب'):esc(m.username||'لاعب');
return '
'; return; }
sb.from('notifications').select('id,title,body,url,read,created_at').eq('user_id',window._uid).neq('scope','broadcast').order('id',{ascending:false}).limit(50).then(function(r){
var rows=r.data||[];
box.innerHTML = rows.length? rows.map(function(n){
/* كل تنبيه يأخذك لموضعه — نستنتج الوجهة من الرابط أو من نصّه */
var t=n.title||'', dest=window.notifDest(n);
return '
'+
'صالح '+(d.mins||10)+' دقائق \u00B7 لمرّة واحدة \u00B7 لا تعطه أحداً.
';
}catch(e){
m.style.color='var(--gold)'; m.textContent='رمزك: '+c+' — اكتبه بديسكورد: /اربط';
}
},function(){ m.style.color='var(--red)'; m.textContent='تعذّر توليد الرمز.'; });
};
(function(){
var b=document.getElementById('btnLinkDiscord'); if(!b) return;
b.addEventListener('click',function(){
var m=$('dcLinkMsg'); if(m){ m.style.color='var(--gold)'; m.textContent='...جاري التحويل لـDiscord'; }
if(!sb.auth.linkIdentity){ window.showLinkCode(m); return; }
sb.auth.linkIdentity({provider:'discord', options:{redirectTo: location.origin+'/', scopes:'identify email guilds.join', skipBrowserRedirect:true}}).then(function(r){
if(r&&r.error){
/* ═══ الربط المباشر مقفل من إعدادات المزوّد ═══
نرشده للطريق الذي يعمل اليوم بدل رسالة إنجليزية ما يفهمها. */
var em=String((r.error&&r.error.message)||'');
var code=String((r.error&&r.error.code)||'');
if(m){
if(/manual_linking_disabled/i.test(code)||/manual linking/i.test(em)){
m.style.color='var(--tx2)';
window.showLinkCode(m);
} else if(/identity|already|exists|conflict/i.test(em+code)){
m.style.color='var(--gold)';
m.textContent='⚠️ حساب ديسكورد هذا مربوط بعضوية ثانية بالمنصة — راجع الإدارة.';
} else {
m.style.color='var(--red)'; m.textContent='تعذّر الربط — جرّب أمر /اربط بديسكورد.';
}
}
return;
}
var url=r&&r.data&&r.data.url; if(!url){ if(m) m.textContent='تعذّر إنشاء الرابط'; return; }
/* نفس قاعدة الدخول: آيفون مثبّت يبقى بنافذته كي لا يضيع الربط */
var ua2=navigator.userAgent||'';
var mobile=/iphone|ipad|ipod|android/i.test(ua2);
var inApp2=(window.matchMedia && window.matchMedia('(display-mode: standalone)').matches)
|| window.navigator.standalone===true;
if(!mobile || (inApp2 && /iphone|ipad|ipod/i.test(ua2))){ location.href=url; return; }
var t=setTimeout(function(){ location.href=url; },4000);
fetch('/api/oauth-url?u='+encodeURIComponent(url)).then(function(x){return x.json();})
.then(function(d){ clearTimeout(t);
if(d&&d.url) window.showDiscordChoice('dcLinkMsg', d.deep||d.url, d.url);
else location.href=url; })
.catch(function(){ clearTimeout(t); location.href=url; });
},function(e){ if(m){ m.style.color='var(--red)'; m.textContent='تعذّر الربط'; } });
});
})();
// ضمّه لسيرفر Discord تلقائياً (مرة واحدة لكل جلسة دخول)
window.joinDiscordServer=function(){
try{ if(localStorage.getItem('cx_dcJoined')==='1') return; }catch(e){}
sb.auth.getSession().then(function(r){
var s=r&&r.data&&r.data.session; var pt=s&&s.provider_token;
if(!pt) return; // ما فيه توكن (جلسة قديمة) — يشتغل بالدخول القادم
fetch('/api/discord-join',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({provider_token:pt})})
.then(function(r){ return r.json(); })
.then(function(d){
if(d&&d.ok){
try{ localStorage.setItem('cx_dcJoined','1'); }catch(e){}
if(d.joined) toast('🎉 انضممت لسيرفر Discord تلقائياً!');
}
}).catch(function(){});
},function(){});
};
/* ===== إعدادات الحساب ===== */
window.loadSettings=function(){
if(!window._uid){ go('account'); return; }
if(window.renderBlockList) window.renderBlockList();
sb.from('profiles').select('dm_policy,wall_policy,visits_policy,notif_general').eq('id',window._uid).single().then(function(r){
var p=r.data||{};
if($('stDm')) $('stDm').value=p.dm_policy||'all';
if($('stWall')) $('stWall').value=p.wall_policy||'all';
if($('stVisits')) $('stVisits').value=p.visits_policy||'all';
if($('stGen')) $('stGen').value=(p.notif_general===false)?'0':'1';
if(window.syncPills) window.syncPills();
},function(){});
window.authUser().then(function(u){ var e=u&&u.data&&u.data.user&&u.data.user.email; if($('stEmail')) $('stEmail').placeholder=e?('الحالي: '+e):'البريد الجديد'; });
};
window.loadFriends=function(){
if(!window._uid) return;
sb.from('friends').select('id,requester,addressee,status').or('requester.eq.'+window._uid+',addressee.eq.'+window._uid).then(function(r){
var rows=r.data||[];
var reqs=rows.filter(function(f){return f.status==='pending'&&f.addressee===window._uid;});
var acc=rows.filter(function(f){return f.status==='accepted';});
var ids=[]; rows.forEach(function(f){ ids.push(f.requester,f.addressee); });
ids=ids.filter(function(x){return x!==window._uid;});
window._friendIds=acc.map(function(f){return f.requester===window._uid?f.addressee:f.requester;});
if($('stFrCount')) $('stFrCount').textContent=reqs.length?('('+reqs.length+')'):'';
if(!ids.length){ if($('stFriendReqs')) $('stFriendReqs').innerHTML='
لا طلبات
'; if($('stFriends')) $('stFriends').innerHTML='
لا أصدقاء بعد — أضفهم من قائمة اللاعبين
'; return; }
sb.from('profiles').select('id,username,avatar_url').in('id',ids).then(function(pr){
var pm={}; (pr.data||[]).forEach(function(p){pm[p.id]=p;});
var row=function(pid,extra){ var p=pm[pid]||{}; var av=p.avatar_url?'':''+esc(String(p.username||'?').slice(0,2).toUpperCase())+'';
return '
'+av+'
'+window.dot(pid)+esc(p.username||'لاعب')+'
'+extra+'
'; };
var bs='background:rgba(56,182,255,.1);border:1px solid var(--line2);color:var(--blue2);font-family:inherit;font-size:10px;font-weight:800;padding:5px 9px;border-radius:7px;cursor:pointer';
if($('stFriendReqs')) $('stFriendReqs').innerHTML = reqs.length? reqs.map(function(f){
var other=f.requester;
return row(other,'');
}).join('') : '
';
});
},function(){});
};
(function(){
/* ═══ المستمع على المستند لا على شاشةٍ بعينها ═══
كان مربوطاً بشاشة «الإعدادات» يوم كان الأصدقاء جزءاً منها. ثم
انتقلوا إلى شاشة #friends فبقي المستمع حيث كان — فالأزرار
تُرسم وتُضغط ولا يسمعها أحد. وربطُه بالمستند يُنهي هذا الصنف
من الأعطال: أينما انتقلت البطاقة، الضغطة تصل. */
document.addEventListener('click',function(e){
var t=e.target&&e.target.closest?e.target.closest('[data-fracc],[data-frdel]'):null;
if(!t) return;
var a=t.getAttribute('data-fracc'), d=t.getAttribute('data-frdel');
t.disabled=true; /* ضغطتان لا تُنشئان طلبين */
var done=function(m){ toast(m); if(window.loadFriends) window.loadFriends(); };
var fail=function(r){ t.disabled=false;
toast((r&&r.error&&r.error.message)?('تعذّر: '+r.error.message):'تعذّر — أعد المحاولة'); };
if(a){
sb.from('friends').update({status:'accepted'}).eq('id',a).select('id')
.then(function(r){
if(r&&r.error) return fail(r);
/* صفرُ صفوفٍ يعني أن السياسة حجبته بصمت — لا نقل «تم» كذباً */
if(!r||!r.data||!r.data.length) return fail({error:{message:'ما قدرت أقبله — حدّث الصفحة'}});
done('✅ صار صديقك');
}, fail);
} else if(d){
sb.from('friends').delete().eq('id',d).select('id')
.then(function(r){
if(r&&r.error) return fail(r);
if(!r||!r.data||!r.data.length) return fail({error:{message:'ما قدرت أزيله — حدّث الصفحة'}});
done('تم');
}, fail);
}
});
var sp=document.getElementById('stSavePass');
if(sp) sp.addEventListener('click',function(){
var m=$('stPassMsg'), a=($('stPass1').value||''), b=($('stPass2').value||'');
if(a.length<6){ m.style.color='var(--red)'; m.textContent='6 أحرف على الأقل'; return; }
if(a!==b){ m.style.color='var(--red)'; m.textContent='الكلمتان غير متطابقتين'; return; }
m.style.color='var(--gold)'; m.textContent='...جاري التحديث';
sb.auth.updateUser({password:a}).then(function(r){
if(r&&r.error){ m.style.color='var(--red)'; m.textContent=errAr(r.error.message); return; }
m.style.color='#3ddc84'; m.textContent='✅ تم تحديث كلمة المرور'; $('stPass1').value=''; $('stPass2').value='';
});
});
var se=document.getElementById('stSaveEmail');
if(se) se.addEventListener('click',function(){
/* المعرّفُ في HTML هو stMailMsg — والاختلافُ عنه كان يقتلُ الضغطةَ صامتاً */
var m=$('stMailMsg')||$('stEmailMsg')
||{style:{},set textContent(v){ if(window.toast) toast(v); }};
var e=($('stEmail').value||'').trim();
if(!/^\S+@\S+\.\S+$/.test(e)){ m.style.color='var(--red)'; m.textContent='بريد غير صالح'; return; }
m.style.color='var(--gold)'; m.textContent='...جاري التحديث';
/* ═══ لا نَعِد برسالةٍ قد لا تُرسل ═══
كنّا نقول دائماً «أُرسل رابط تأكيد». وتأكيدُ البريد مُطفأٌ في
إعدادات المنصة، فلا رسالةَ تُرسل والتغيير يقع فوراً — فينتظر
العضو بريداً لا يأتي ويظنّ الأمر معطّلاً. الردّ نفسه يقول
الحقيقة: إن حمل new_email فالتأكيد مطلوب، وإلا فقد تمّ. */
sb.auth.updateUser({email:e}).then(function(r){
if(r&&r.error){ m.style.color='var(--red)'; m.textContent=errAr(r.error.message); return; }
var u=r&&r.data&&r.data.user, pending=!!(u&&u.new_email);
m.style.color='#3ddc84';
if(pending){
m.textContent='📧 أُرسل رابط تأكيد إلى «'+e+'» — افتحه لإكمال التغيير';
}else{
m.textContent='✅ صار بريدك «'+((u&&u.email)||e)+'» — استعمله بالدخول من الآن';
$('stEmail').value=''; $('stEmail').placeholder='الحالي: '+((u&&u.email)||e);
}
},function(){ m.style.color='var(--red)'; m.textContent='تعذّر الاتصال — أعد المحاولة'; });
});
var sv=document.getElementById('stSavePriv');
if(sv) sv.addEventListener('click',function(){
var m=$('stPrivMsg');
sb.from('profiles').update({dm_policy:$('stDm').value, wall_policy:$('stWall').value,
visits_policy: (($('stVisits')||{}).value||'all'),
notif_general: (($('stGen')||{}).value!=='0')}).eq('id',window._uid).then(function(r){
if(r&&r.error){ m.style.color='var(--red)'; m.textContent=errAr(r.error.message); return; }
m.style.color='#3ddc84'; m.textContent='✅ حُفظت إعدادات الخصوصية';
});
});
})();
/* ===== الأصدقاء + الجدار من ملف اللاعب ===== */
window.addFriend=function(pid){
if(!window._uid){ go('account'); return; }
sb.from('friends').insert({requester:window._uid,addressee:pid,status:'pending'}).then(function(r){
if(r&&r.error){ toast(/duplicate|unique/i.test(r.error.message||'')?'الطلب مُرسل مسبقاً':'تعذّر الإرسال'); return; }
toast('👥 أُرسل طلب الصداقة');
window.loadPlayer&&window.loadPlayer();
});
};
window.loadWall=function(pid){
var box=$('plWall'); if(!box) return;
sb.from('wall_posts').select('id,author_id,body,created_at,reply_to').eq('profile_id',pid).order('id',{ascending:false}).limit(60).then(function(r){
var rows=r.data||[]; var ids=rows.map(function(x){return x.author_id;});
if(!rows.length){ box.innerHTML='
لا تعليقات بعد — كن الأول 👋
'; return; }
sb.from('profiles').select('id,username,avatar_url,style_id').in('id',ids).then(function(pr){
var pm={}; (pr.data||[]).forEach(function(p){pm[p.id]=p;});
var kids={}; rows.forEach(function(w){ if(w.reply_to){ (kids[w.reply_to]=kids[w.reply_to]||[]).push(w); } });
Object.keys(kids).forEach(function(k){ kids[k].sort(function(a,b){return a.id-b.id;}); });
var one=function(w,isReply){
var a=pm[w.author_id]||{}, sz=isReply?25:30;
var av=a.avatar_url?'':''+esc(String(a.username||'?').slice(0,2).toUpperCase())+'';
var can=(window._uid===w.author_id)||(window._uid===pid)||window._isStaff;
var wf=window.fxOf(w.author_id,a.style_id);
var wNm=wf?window.fxName(wf,a.username||'لاعب',true):(''+esc(a.username||'لاعب')+'');
var wb=window.fxBubble(wf);
return '
'+av+
'
'+wNm+''+tDT(w.created_at)+'
'+
'
'+esc(w.body||'')+'
'+
(window._uid&&!isReply?'':'')+
'
'+
(can?'':'')+'
';
};
box.innerHTML=rows.filter(function(w){return !w.reply_to;}).map(function(w){
var rep=(kids[w.id]||[]).map(function(k){return one(k,true);}).join('');
return one(w,false)+(rep?'
'+rep+'
':'');
}).join('');
});
},function(){});
};
window.replyWall=function(id,name){
window._wallReply=id;
var bar=$('plReplyTo'), tx=$('plReplyTx'), i=$('plWallInput');
if(tx) tx.textContent='↩ ترد على '+name;
if(bar) bar.hidden=false;
if(i){ i.focus(); i.scrollIntoView({behavior:'smooth',block:'center'}); }
};
window.cancelReply=function(){ window._wallReply=null; var b=$('plReplyTo'); if(b) b.hidden=true; };
/* عضو في كلان ⇒ ما يبحث عن كلان (منطق بديهي) */
window.applyClanContext=function(){
var cid=window._myClanId||window._myMemberClanId;
var tag=window._myClanTag||window._myMemberClanTag||'';
var owner=!!window._myClanId;
var inClan=!!cid;
/* «أبحث عن كلان» لا تُعرض إلا لمن نتيقّن أنه بلا كلان */
var sc=$('seekCard'); if(sc) sc.hidden = inClan || !window._uid;
var cs=$('cdirSeek'); if(cs) cs.hidden = inClan || !window._uid;
var crb=$('cdirRegBtn'); if(crb) crb.hidden = inClan;
var af=$('agFindClan'); if(af) af.hidden = inClan;
var sk=document.getElementById('seek');
if(inClan && sk && sk.classList.contains('active')) go('clandir');
/* ═══ مجموعة «كلاني» ═══ صفوف تُلخّص حالتك في الكلان */
var box=$('acClanRows'), grp=$('acGrpClan');
if(!box||!grp) return;
var R=function(ic,t,sub2,val,cls,act){
return '';
};
if(inClan){
grp.hidden=false;
var rl = window._myMemberRole==='leader' ? 'رئيس الكلان'
: (window._myMemberRole==='vice' ? 'نائب الرئيس'
: (window._myMemberRole==='coordinator' ? 'منسق كلان' : 'عضو'));
var ric = window._myMemberRole==='leader' ? '👑'
: (window._myMemberRole==='vice' ? '🎖️'
: (window._myMemberRole==='coordinator' ? '📋' : '🎮'));
var h2='';
/* صفٌّ واحد: صاحب الكلان يدخل الإدارة مباشرة، وغيره يفتح صفحة كلانه */
h2 += R(ric, 'كلاني',
'CLAN '+(tag||'؟')+' · أنت '+rl,
'', '',
canManage ? "go('manageclan')" : ("openClan('"+cid+"')"));
/* الرئيس والنائب يديران الكلان، العضو العادي يغادر */
var canManage = owner || window._myMemberRole==='leader' || window._myMemberRole==='vice';
if(!canManage)
h2 += '';
box.innerHTML=h2;
} else {
grp.hidden=false;
var base = R('🚪','ابحث عن كلان','كلانات تستقبل أعضاء الآن','','', "go('hiring')");
/* عنده كلان مؤرشف؟ نوجّهه للأرشيف بدل تسجيل كلان جديد سيُرفض */
sb.from('clans').select('id,tag').eq('created_by',window._uid||'00000000-0000-0000-0000-000000000000')
.eq('status','archived').is('deleted_at',null).limit(1).then(function(ar){
var a=ar&&ar.data&&ar.data[0];
box.innerHTML = (a
? R('🔒','كلانك CLAN '+String(a.tag)+' بالأرشيف','استرجعه أو احذفه لتسجّل غيره','بالأرشيف','off', "go('archive')")
: R('👑','سجّل كلانك','تصير رئيسه وتضيف أعضاءك','','', "go('createclan')")) + base;
},function(){ box.innerHTML = R('👑','سجّل كلانك','تصير رئيسه وتضيف أعضاءك','','', "go('createclan')") + base; });
}
/* شريط الإحصاءات: تاق كلانك */
var sc=$('acStClan'); if(sc) sc.textContent = inClan ? (tag||'—') : '—';
};
window.postWall=function(pid){
if(_blocked()) return;
var i=$('plWallInput'); if(!i) return; var b=(i.value||'').trim(); if(!b) return; if(b.length>300) b=b.slice(0,300);
if(!window._uid){ go('account'); return; }
var row={profile_id:pid,author_id:window._uid,body:b};
if(window._wallReply) row.reply_to=window._wallReply;
sb.from('wall_posts').insert(row).then(function(r){
if(r&&r.error){ toast(/policy|violates/i.test(r.error.message||'')?'🔒 هذا اللاعب يقيّد الكتابة على جداره':'تعذّر النشر: '+String(r.error.message||'').slice(0,60)); return; }
i.value=''; window.cancelReply(); window.loadWall(pid);
/* الإشعار تُنشئه القاعدة بمحفّز trg_notify_wall — كان المتصفّح
يحاول إنشاءه فيُردّ، لأن العضو لا يملك أن يُشعر غيره. */
});
};
(function(){
var p=document.getElementById('player'); if(!p) return;
p.addEventListener('click',function(e){ var t=e.target; if(!t.getAttribute) return;
var d=t.getAttribute('data-wdel');
if(d) return sb.from('wall_posts').delete().eq('id',d).then(function(){ window.loadWall(window._viewPlayer); });
var rp=t.getAttribute('data-wrep');
if(rp) window.replyWall(rp, t.getAttribute('data-wrepn')||'لاعب');
});
})();
/* ═══ تجربة اللمسات على المنصة نفسها ═══
?fxtest=clash | win | lose — عرضٌ محليّ على جهاز الفاتح وحده،
لا يمسّ أحداً ولا يكتب شيئاً. */
try{
var _fm=/[?&]fxtest=(clash|win|lose)/.exec(location.search||'');
if(_fm){
setTimeout(function(){
if(_fm[1]==='clash') window.fxClash('RLX','BATL','🔭 سنايبر · أفضل من 6', null);
else window.fxResult({mine:'RLX', opp:'BATL',
myScore:_fm[1]==='lose'?1:4, oppScore:_fm[1]==='lose'?4:1,
won:_fm[1]!=='lose', pts:18});
}, 2200);
}
}catch(e){}
/* ═══════════ وضع الإطلاق ═══════════
الحارس في القاعدة يرفض أصلاً — هذي الواجهة تقول ذلك بلطفٍ قبل
المحاولة: سجّلوا كلاناتكم، والتحديات على الأبواب. */
window.applyLaunchMode=function(){
var off=((window._appSet||{}).challenges_open==='0');
var btn=document.getElementById('btnGoNewCh');
var fab=document.getElementById('fabCh');
var note=document.getElementById('launchNote');
if(off){
if(btn) btn.style.display='none';
if(fab) fab.style.display='none';
if(!note){
var sec=document.getElementById('challenges');
var sub=sec?sec.querySelector('.ssub'):null;
if(sub){
note=document.createElement('div');
note.id='launchNote';
note.style.cssText='margin:4px 14px 12px;border-radius:15px;padding:14px 15px;text-align:center;'+
'background:linear-gradient(135deg,rgba(232,179,75,.14),rgba(232,179,75,.03));'+
'border:1px solid rgba(232,179,75,.4);font-size:12.5px;line-height:2';
note.innerHTML='🚀 المنصة في مرحلة التسجيل '+
'سجّلوا كلاناتكم وجهّزوا صفوفكم — التحديات تُفتح قريباً ⚔️ '+
'🛡️ سجّل كلانك الآن';
sub.parentNode.insertBefore(note, sub.nextSibling);
}
} else note.hidden=false;
} else {
if(btn) btn.style.display='';
if(fab) fab.style.display='';
if(note) note.hidden=true;
}
};
/* تغيّر المفتاح من اللوحة يصل حيّاً — الإعدادات مُشترَك عليها أصلاً */
setInterval(function(){
var sc=document.getElementById('challenges');
if(sc && sc.classList.contains('active') && window.applyLaunchMode) window.applyLaunchMode();
}, 5000);
/* ═══ صفحة القوانين: شريطٌ يتبع قراءتك، وأقسام تظهر بهدوء ═══ */
(function(){
var sec=document.getElementById('rules'); if(!sec) return;
var nav=document.getElementById('rlNav');
if(nav) nav.addEventListener('click',function(e){
var b=e.target.closest('button[data-rl]'); if(!b) return;
var t=document.getElementById(b.getAttribute('data-rl'));
if(t) t.scrollIntoView({behavior:'smooth',block:'start'});
});
/* ═══ باحث القوانين ═══
يكتب «تيباق» فيجد T-Bag — البحث يتجاهل التشكيل وصور الحروف. */
function rlNorm(t){
return String(t==null?'':t)
.replace(/[\u064B-\u0652\u0670\u0640]/g,'')
.replace(/[\u0622\u0623\u0625\u0671]/g,'\u0627')
.replace(/\u0629/g,'\u0647').replace(/\u0649/g,'\u064A')
.toLowerCase();
}
/* المصطلحات الأجنبية تُكتب كما تُنطق: «تيباق» تجد T-Bag */
var RL_ALIAS=[[/تي ?باق|تيباك/g,'t-bag'],[/كيل ?ستريك/g,'killstreak'],
[/سبيكتيت|سبكتيت/g,'spectate'],[/باور ?دايركتور|بور ?دايركتور/g,'powerdirector'],
[/كيل ?كام/g,'kill cam'],[/تايم ?اوت/g,'time out'],[/اوفلاين|أوفلاين/g,'offline'],
[/كلان ?تاق|كلان ?تاغ/g,'clan tag'],[/فيرست ?بلود/g,'first blood'],[/قلتش|جلتش/g,'قلتش']];
var rlQ=document.getElementById('rlQ');
if(rlQ) rlQ.addEventListener('input',function(){
var q=rlNorm(rlQ.value.trim());
RL_ALIAS.forEach(function(a){ q=q.replace(a[0],a[1]); });
var n=0;
[].forEach.call(sec.querySelectorAll('.rl__sec'),function(sc){
var any=false;
[].forEach.call(sc.querySelectorAll('.rl__r, .rl__note'),function(r){
var hit=!q || rlNorm(r.textContent).indexOf(q)!==-1;
r.classList.toggle('rlhide', !hit);
if(hit && q) n++;
if(hit) any=true;
});
/* العناوين الفرعية والرسوم تظهر مع قسمها كاملاً أو تختفي مع البحث */
[].forEach.call(sc.querySelectorAll('.rl__sub, .rl__zoom, .rl__stats'),function(x){
x.classList.toggle('rlhide', !!q && !any ? true : !!q);
});
sc.classList.toggle('rlhide', !!q && !any);
});
var c=document.getElementById('rlQN');
if(c) c.textContent = q ? (n? ('🔎 '+n+' قاعدة مطابقة') : 'ما فيه قاعدة تطابق بحثك — جرّب كلمة أقصر') : '';
var nv=document.getElementById('rlNav');
if(nv) nv.style.display = q ? 'none' : '';
});
var io1=null;
window._rlBoot=function(){
if(io1) return;
try{
io1=new IntersectionObserver(function(es){
es.forEach(function(en){
if(en.isIntersecting){
en.target.classList.add('vis');
var id=en.target.id;
if(nav) [].forEach.call(nav.querySelectorAll('button'),function(b){
b.classList.toggle('on', b.getAttribute('data-rl')===id);
});
}
});
},{threshold:.25});
[].forEach.call(sec.querySelectorAll('.rl__sec'),function(x){ io1.observe(x); });
}catch(e){ [].forEach.call(sec.querySelectorAll('.rl__sec'),function(x){ x.classList.add('vis'); }); }
/* شبكة أمان: إن لم يعمل الكشف لأي سبب، تظهر الأقسام كلها —
الحركة زينة، والمحتوى لا يُرهن بها */
setTimeout(function(){
[].forEach.call(sec.querySelectorAll('.rl__sec:not(.vis)'),function(x){ x.classList.add('vis'); });
}, 1200);
};
})();
/* ═══════════════ التواصل مع الإدارة ═══════════════
تذكرة برقمٍ مميز #T، والردّ على التذكرة نفسها لا برسائل متناثرة —
وكل ردٍّ يوصل صاحبه إشعاراً على الجرس والجوال. */
var TK_ST={ open: ['⏳ بانتظار الردّ','rgba(255,204,0,.13)','rgba(255,204,0,.4)','var(--gold)'],
answered: ['✅ تم الردّ','rgba(61,220,132,.13)','rgba(61,220,132,.4)','#3ddc84'],
closed: ['🔒 مغلقة','rgba(255,255,255,.05)','var(--line)','var(--tx3)'] };
function tkChip(st){
var c=TK_ST[st]||TK_ST.open;
return ''+c[0]+'';
}
function tkLife(ts){
var ttl=window._tkTTL||30;
var ms=typeof ts==='number'?ts:new Date(ts).getTime();
var rem=ms+ttl*86400000-Date.now();
if(rem<=0) return '⏱️ منتهية';
var d=Math.floor(rem/86400000), h=Math.floor((rem%86400000)/3600000);
if(d>0) return '⏱️ '+d+' يوم'+(h?' و'+h+' س':'')+'';
return '⏱️ '+h+' ساعة';
}
/* ═══ تذاكر الزائر على جهازه ═══ المفتاح السرّي لا يغادر التخزين المحلي */
function gtkAll(){ try{ return JSON.parse(localStorage.getItem('codar_gtk')||'[]')||[]; }catch(e){ return []; } }
function gtkSave(list){ try{ localStorage.setItem('codar_gtk', JSON.stringify(list.slice(-30))); }catch(e){} }
function gtkFind(id){ return gtkAll().filter(function(t){ return t.id===id; })[0]||null; }
/* ═══ الأنواعُ للزائرِ أيضاً ═══
الكتلةُ داخلَ `loadSupport` تقرؤها **بعدَ** خروجِ الزائر، فيبقى على
قائمةٍ مكتوبةٍ في الوسمِ لا تُطابِقُ اللوحة. وهذه تُنادى قبلَ الخروج.
وهي أيضاً بابُ `?s=support&kind=…` الذي تنزلُ عليه إعلاناتُ الرئيسة. */
window._supKinds=function(){
sb.from('settings').select('key,value').in('key',['ticket_kinds','ticket_off']).then(function(r){
var m={}; ((r&&r.data)||[]).forEach(function(x){ m[x.key]=x.value; });
var sel=$('supKind'); if(!sel) return;
try{
var arr=JSON.parse(m.ticket_kinds||'null');
if(Array.isArray(arr) && arr.length)
sel.innerHTML=arr.map(function(k){ return ''; }).join('');
}catch(e){}
/* ═══ نوعٌ جاءَ من رابطِ إعلان ═══
يُطابَقُ بالاحتواءِ لا بالتساوي: الأنواعُ تُحرَّرُ من اللوحةِ فقد
يتغيّرُ إيموجيها أو تُزادُ كلمة، **ورابطُ الإعلانِ منشورٌ لا يُعدَّل**.
ولا يُفتَحُ النموذجُ إن كان الاستقبالُ موقوفاً — وعدٌ لا يُوفى. */
var want=String(window._tkKind||''); if(!want) return;
window._tkKind=null;
if(m.ticket_off==='1') return;
var hit=null;
for(var k=0;k=0 || want.indexOf(v)>=0){ hit=v; break; }
}
if(!hit) return;
sel.value=hit;
var fm=$('supForm'); if(fm) fm.hidden=false;
setTimeout(function(){ try{ (fm||sel).scrollIntoView({block:'center',behavior:'smooth'}); }catch(e){} },320);
},function(){});
};
window.loadSupport=function(){
var box=$('supList'); if(!box) return;
window._supKinds();
var contact=$('supContact'); if(contact) contact.style.display=window._uid?'none':'';
sb.rpc('is_apply_open').then(function(r){
var d=r&&r.data; if(!d) d={open:false,ttl:30};
var open=d.open===true;
window._tkTTL=d.ttl||30;
if($('applyOn')) $('applyOn').hidden=!open;
if($('applyOff')) $('applyOff').hidden=open;
if($('applyContact')) $('applyContact').style.display=window._uid?'none':'';
var tn=$('tkTTLNote'); if(tn) tn.innerHTML='⏱️ التذكرة صالحة لمدة '+window._tkTTL+' يوم';
},function(){});
if(!window._uid){
var mine=gtkAll().slice().reverse();
var c0=$('supCount'); if(c0) c0.textContent=mine.length?(mine.length+' تذكرة على هذا الجهاز'):'';
box.innerHTML = mine.length ? mine.map(function(t){
return '
خسرتم هذه — والسجل يُبنى على المدى. تنتظركم جولة ثانية.
')+
'';
el.classList.add('on');
clearTimeout(window._fxT2); window._fxT2=setTimeout(function(){ window.fxClose('fxRes'); }, 7000);
if(!won) return;
var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var target=Math.abs(Number(o.pts||0))||0, cur=0, p=document.getElementById('fxPts');
if(!p) return;
if(reduce||!target){ p.textContent='+'+target+' نقطة'; return; }
var step=function(){ cur++; p.textContent='+'+cur+' نقطة'; if(cur';
};
window.refreshDots=function(){
[].forEach.call(document.querySelectorAll('.pdot[data-pres]'), function(el){
el.classList.toggle('on', window.isOnline(el.getAttribute('data-pres')));
});
if(window.radarSync) window.radarSync(); /* قلب الرادار يتبع الحضور */
if(window.loadStaffOnline) window.loadStaffOnline();
};
window.loadPresence=function(){
var since=new Date(Date.now()-150000).toISOString();
return sb.from('profiles').select('id,last_seen_at').gt('last_seen_at',since).then(function(r){
var m={}; (r.data||[]).forEach(function(p){ m[p.id]=Date.parse(p.last_seen_at); });
window._pres=m; window._presLoaded=true; window.refreshDots();
/* شريط الدردشة يتنفس مع الحضور */
try{
var chs=document.getElementById('chat');
if(chs && chs.classList.contains('active') && window.renderChatBar) window.renderChatBar();
}catch(e){}
/* القائمة معروضة الآن؟ نعيد ترتيبها بالحضور الجديد */
try{
var pl=document.getElementById('players');
if(pl && pl.classList.contains('active') && window.renderPlayers) window.renderPlayers();
}catch(e){}
},function(){});
};
(function(){
/* ═══ النبضة تُرسل فعلاً ═══
طلبات سوبابيس كسولة: لا تنطلق حتى تُطلب نتيجتها بـ then — وبلا
ذلك كانت الدالة تُبنى ولا تُنادى، فما وصل الخادمَ نبضٌ قطّ. */
/* يُفحَصُ عندَ أوّلِ نبضةٍ بعدَ الدخولِ وعندَ العودةِ للتبويب */
(function(){
var done=false;
var go=function(){ if(done||!window._uid||!window.awCheckUnseen) return; done=true;
setTimeout(window.awCheckUnseen, 1200); };
setTimeout(go, 2500);
document.addEventListener('visibilitychange', function(){
if(!document.hidden && window.awCheckUnseen && window._uid) window.awCheckUnseen();
});
})();
window.pingPresence=function(){
if(!window._uid) return;
try{ sb.rpc('ping_presence').then(function(){ window._pinged=true; }, function(){}); }catch(e){}
};
function tick(){
if(document.hidden) return;
window.pingPresence();
window.loadPresence();
}
/* ═══ بصمة الاتصال ═══
المتصفح لا يعرف عنوانه — خادمنا هو من يراه. نناديه بهويةٍ موقّعة
فيسجّلها، وبها تُكشف الحسابات التي يديرها شخصٌ واحد. */
window.markSeen=function(){
if(!window._uid) return;
/* تبويبُ الانتحال لا يبصم عنوانَ المالك باسم العضو */
if(window.IMP && window.IMP.on) return;
sb.auth.getSession().then(function(r){
var t=r&&r.data&&r.data.session&&r.data.session.access_token;
if(!t) return;
fetch('/api/seen',{method:'POST',headers:{Authorization:'Bearer '+t}}).catch(function(){});
},function(){});
};
/* بصمة الجهاز — مرّةً واحدة لكل جلسة، لا حاجة للتكرار كالحضور */
window._fpSent=false;
window.sendFingerprint=function(){
if(!window._uid || window._fpSent) return;
if(window.IMP && window.IMP.on) return;
if(!window.FingerprintJS) { setTimeout(window.sendFingerprint, 1500); return; }
window._fpSent=true;
FingerprintJS.load().then(function(fp){ return fp.get(); }).then(function(result){
sb.auth.getSession().then(function(r){
var t=r&&r.data&&r.data.session&&r.data.session.access_token;
if(!t) return;
fetch('/api/fingerprint',{method:'POST',headers:{'Content-Type':'application/json',Authorization:'Bearer '+t},
body:JSON.stringify({fp:result.visitorId})}).catch(function(){});
},function(){});
}).catch(function(){});
};
/* أول نداء بمجرّد اكتمال الدخول، ثم عند كل عودةٍ للتطبيق — فيمتلئ
السجلّ من أوّل زيارة بدل انتظار دورةٍ كاملة */
var _seenT=setInterval(function(){
if(window._uid){ window.markSeen(); window.sendFingerprint(); clearInterval(_seenT); }
}, 1200);
setTimeout(function(){ clearInterval(_seenT); }, 60000);
setInterval(window.markSeen, 15*60*1000);
document.addEventListener('visibilitychange',function(){
if(!document.hidden) window.markSeen();
});
/* الحضور يُقرأ فوراً لا بعد ثانيتين — الرادار يُرسم أول لحظة */
window.loadPresence();
setTimeout(tick,2500); setInterval(tick,60000);
/* أول نبضة فور اكتمال الدخول — لا ننتظر دورة الدقيقة */
var t0=setInterval(function(){
if(window._uid){ window.pingPresence(); window.loadPresence(); clearInterval(t0); }
}, 1500);
setTimeout(function(){ clearInterval(t0); }, 45000);
document.addEventListener('visibilitychange',function(){ if(!document.hidden) tick(); });
})();
/* ===== قائمة اللاعبين + البحث ===== */
window.loadPlayers=function(){
var box=$('playersList'); if(!box) return;
sb.from('profiles').select('id,username,psn_id,avatar_url,title,wins,losses,points,streak,seeking_clan,role,created_at,style_id').order('points',{ascending:false}).limit(300).then(function(r){
window._allPlayers=(r.data||[]).filter(function(p){ return p.username; });
sb.from('clan_members').select('profile_id,clans(tag)').not('profile_id','is',null).then(function(cm){
var m={}; (cm.data||[]).forEach(function(x){ if(x.clans) m[x.profile_id]=String(x.clans.tag||''); });
window._playerClan=m; window.renderPlayers();
},function(){ window._playerClan={}; window.renderPlayers(); });
});
};
window.renderPlayers=function(){
var box=$('playersList'); if(!box) return;
/* حين يبحثُ العضوُ نعرِضُ ما ردَّته القاعدةُ لا ما صادفَ أن حُمِّل */
var all=(window._plFound!=null)?window._plFound:(window._allPlayers||[]),
cm=window._playerClan||{};
var q=(window._plSearch||'').trim().toLowerCase(), f=window._plFilter||'all';
var list=all.filter(function(p){
if(f==='online' && !(window.isOnline&&window.isOnline(p.id))) return false;
if(q && window._plFound==null){ var s=((p.username||'')+' '+(p.psn_id||'')+' '+(p.title||'')).toLowerCase(); if(s.indexOf(q)===-1) return false; }
return true;
});
/* ═══ من هو داخل الآن يتصدّر ═══
الحاضر أولى بالظهور ممن غاب — تُرسل له رسالة أو تتحدّاه فيردّ. */
if(f==='all' && window.isOnline){
list=list.slice().sort(function(a,b){
var oa=window.isOnline(a.id)?1:0, ob=window.isOnline(b.id)?1:0;
if(oa!==ob) return ob-oa;
return (b.points||0)-(a.points||0);
});
}
/* عدّاد الحاضرين على الحبّة نفسها */
var _on=$('plOnN');
if(_on && window.isOnline){
var n=(window._allPlayers||[]).filter(function(p){ return window.isOnline(p.id); }).length;
_on.textContent = n?('· '+n):'';
}
/* «الكل» يبقى على التصنيف بالنقاط — والباقي بتاريخ الانضمام */
if(f==='new'||f==='old'){
var dir=(f==='new')?-1:1;
list=list.slice().sort(function(a,b){
return dir*String(a.created_at||'').localeCompare(String(b.created_at||''));
});
}
if(!list.length){
box.innerHTML='
'+
(f==='online' ? 'ما فيه أحد متصل الآن 🌙 جرّب «الكل» — تقدر تراسلهم ويردّون لاحقاً'
: 'ما فيه لاعب بهذا البحث 🔎')+'
';
return;
}
box.innerHTML='
'+list.length+' لاعب
'+list.map(function(p){
var av=p.avatar_url?'':''+esc(String(p.username||'?').slice(0,2).toUpperCase())+'';
var ttl=p.title?' '+esc(p.title)+'':'';
var cl=cm[p.id]?'🛡️ CLAN '+esc(cm[p.id])+'':(p.seeking_clan?'🔎 يبحث عن كلان':'بدون كلان');
var st=p.role==='admin'?' 🛡️':(p.role==='moderator'?' 🧑⚖️':'');
var fire=(p.streak>=2)?' 🔥':'';
return '
';
});
};
window.renderBlacklist=function(){
var box=$('blacklistList'); if(!box||!window._blData) return;
var q=(window._blSearch||'').trim().toLowerCase();
var f=window._blFilter||'all';
var items=window._blData.filter(function(b){
if(f!=='all' && b.target_type!==f) return false;
if(q && (b.banned_name||'').toLowerCase().indexOf(q)===-1) return false;
return true;
});
if(!items.length){ box.innerHTML='
ما فيه نتائج مطابقة
'; return; }
box.innerHTML=items.map(function(b){
var isClan=b.target_type==='clan';
var badge=isClan?'كلان'
:'لاعب';
var dur;
if(b.days===null||b.days===undefined){
dur='دائم';
} else {
var rem=b.expires_at?Math.max(0,Math.ceil((new Date(b.expires_at)-new Date())/86400000)):0;
dur='باقي '+rem+' يوم
'+b.days+' يوم
';
}
return '
'+
'
🚫
'+
'
'+
'
'+esc(b.banned_name||'—')+''+badge+'
'+
'
'+esc(b.reason||'')+'
'+
'
'+
'
'+dur+'
'+
'
';
}).join('');
};
(function(){
var s=$('blSearch'); if(s) s.addEventListener('input',function(){ window._blSearch=s.value; window.renderBlacklist&&window.renderBlacklist(); });
var f=$('blFilter'); if(f) f.querySelectorAll('button').forEach(function(b){ b.addEventListener('click',function(){ f.querySelectorAll('button').forEach(function(x){x.classList.remove('on')}); b.classList.add('on'); window._blFilter=b.getAttribute('data-f'); window.renderBlacklist&&window.renderBlacklist(); }); });
})();
/* ===== الرسائل المباشرة ===== */
window.openDM=function(pid,uname){ window._dmWith=pid; window._dmName=uname||''; go('dm'); window.loadDM(); };
window.loadDM=function(){
var box=$('dmMsgs'), pid=window._dmWith; if(!box||!pid||!window._uid) return;
if($('dmSub')) $('dmSub').innerHTML=
(window.isOnline(pid)?' متصل الآن · ':'')+
'عرض ملفه ←';
/* الاسم والصورة: نرسم المعروف فوراً ثم نُصحّحه من ملفه */
var drawHead=function(p){
var nm=(p&&p.username)||window._dmName||'محادثة';
var f=window.fxOf(pid);
if($('dmTitle')) $('dmTitle').innerHTML=(f?window.fxName(f,nm):esc(nm))+(window.rkPin?window.rkPin(pid,22):'');
var av=$('dmAv'); if(!av) return;
av.onclick=function(){ openPlayer(pid); };
av.innerHTML=(p&&p.avatar_url)
? ''
: esc(String(nm).slice(0,2).toUpperCase());
};
drawHead(null);
sb.from('profiles').select('username,avatar_url').eq('id',pid).single()
.then(function(r){ if(r&&r.data){ window._dmName=r.data.username||window._dmName; drawHead(r.data); } },function(){});
sb.from('dms').select('id,from_id,to_id,body,created_at').or('and(from_id.eq.'+window._uid+',to_id.eq.'+pid+'),and(from_id.eq.'+pid+',to_id.eq.'+window._uid+')').order('id',{ascending:false}).limit(80).then(function(r){
var rows=(r.data||[]).slice().reverse();
box.innerHTML = rows.length? rows.map(function(m){
var own=m.from_id===window._uid, tm=tHM(m.created_at);
var db=window.fxBubble(window.fxOf(m.from_id));
var dbub = db
? '
';
}).join('');
});
});
};
window.openPlayer=function(pid){ if(pid) window._viewPlayer=pid; go('player'); }; // go() تستدعي loadPlayer — لا نكرّرها
/* ═══════════════════════════════════════════════════════════
آخرُ الزوّار
───────────────────────────────────────────────────────────
صفٌّ واحدٌ لكلِّ زائرٍ في القاعدة لا صفٌّ لكلِّ فتحةِ صفحة — فالقائمةُ
«آخرُ من زار» لا «كم مرّةً دخل». وثلاثةٌ لا تُسجَّل: زيارةُ المرءِ
ملفَّ نفسِه، والزائرُ غيرُ المسجَّل، و**وضعُ الدخولِ بحسابِ عضو** —
فالدخولُ بحسابِ غيرِك لا يُخلّفُ أثراً باسمِه، وهو مبدأٌ مستقرٌّ
في المنصةِ لا يُنقَض. */
window.loadVisitors=function(pid){
var box=$('plVisitors'); if(!box||!pid) return;
var imp=false;
try{ imp = sessionStorage.getItem('codar_imp_on')==='1'; }catch(e){}
var isMe = (window._uid===pid);
var draw=function(){
sb.rpc('profile_visitors',{p_profile:pid, p_limit:12}).then(function(r){
var L=(r&&r.data)||[];
if(!L.length){ box.innerHTML=''; return; } /* بلا زوّار: لا قسمَ فارغاً */
var ago=function(t){
var d=(Date.now()-new Date(t).getTime())/1000;
if(d<60) return 'الآن';
if(d<3600) return Math.floor(d/60)+'د';
if(d<86400) return Math.floor(d/3600)+'س';
if(d<604800) return Math.floor(d/86400)+'ي';
return Math.floor(d/604800)+'أ';
};
var card=function(x){
var nm=x.name||'لاعب';
var av=x.avatar
? ''
: ''+esc(String(nm).slice(0,2).toUpperCase())+'';
var f=window.fxOf(x.id);
return '';
};
box.innerHTML=
'
'+
'
👣 آخر الزوّار ('+L.length+')
'+
'
'+L.map(card).join('')+'
'+
'
';
},function(){ box.innerHTML=''; });
};
/* نُسجّلُ أوّلاً ثمّ نقرأُ — كي يرى الزائرُ نفسَه في القائمةِ فوراً */
if(window._uid && !isMe && !imp){
sb.rpc('note_profile_visit',{p_profile:pid}).then(draw, draw);
} else { draw(); }
};
window.loadPlayer=function(){
var box=$('playerBody'), pid=window._viewPlayer; if(!box||!pid) return;
box.innerHTML='
⏳ جاري التحميل...
';
sb.from('profiles').select('id,username,psn_id,avatar_url,wins,losses,points,streak,seeking_clan,role,bio,title,style_id').eq('id',pid).single().then(function(pr){
var p=pr.data; if(!p){ box.innerHTML='
اللاعب غير موجود
'; return; }
sb.from('profiles').select('id',{count:'exact'}).limit(1).gt('points',p.points||0).then(function(rk){
var rank=(rk.count||0)+1, medal=rank===1?'🥇':rank===2?'🥈':rank===3?'🥉':('#'+rank);
var t=(p.wins||0)+(p.losses||0), ratio=t?Math.round((p.wins||0)*100/t):0;
var ranked=((p.points||0)>0||t>0); // بدون مباريات = غير مصنّف (ما نعطيه 🥇 على الفاضي)
var av=p.avatar_url?'':''+esc(String(p.username||'?').slice(0,2).toUpperCase())+'';
var isMe=(window._uid===p.id);
/* الشارة من مجموعته إن فُعّلت، وإلا الشارة المبنيّة على الرتبة */
var _pf=window.fxOf(p.id), badge=window.fxBadge(_pf,10);
if(!badge) badge=p.role==='admin'?'🛡️ إداري':(p.role==='moderator'?'🧑⚖️ مشرف':'');
var seek=p.seeking_clan?'🔎 يبحث عن كلان':'';
/* ترويسة الملف: بطاقة واحدة تجمع الصورة والاسم والشارات */
var head='
';
/* الخزانةُ بعدَ الإحصاءاتِ لا في ذيلِ الصفحة: كانت تحتَ جدارِ
الملفِّ فلا تُرى إلّا بعدَ تمريرٍ طويل — **وخزانةُ أوسمةٍ في
القاعِ ليست خزانة.** */
box.innerHTML=head+''+stats+
''+
''+
''+
'
📜 سجل المباريات الفردية
'+
'
💌 جدار الملف
'+
(window._uid?'
'+
'':'')+
'
';
/* ═══ النداءُ ينتظرُ تعريفَه ═══
التعريفُ في كتلةِ سكربتٍ تأتي بعدَ هذه، **و`try/catch` كان
يبتلعُ الخطأَ صامتاً** فلم تظهرِ الخزانةُ ولا ظهرَ سببُ غيابِها.
**الكتمُ الصامتُ أسوأُ من العطلِ الصريح** — العطلُ يُرى فيُصلَح.
فيُنتظَرُ التعريفُ ثانيتَين ثمَّ يُشتكى في وحدةِ التحكّم. */
(function(_pid){
var _t = 0;
var _awWait = setInterval(function(){
if (window.renderAwards) {
clearInterval(_awWait);
try { window.renderAwards(_pid, 'plAwards'); }
catch(e){ console.warn('[awards] فشل الرسم:', e); }
} else if (++_t > 40) {
clearInterval(_awWait);
console.warn('[awards] renderAwards غير معرّفة بعد ثانيتين');
}
}, 50);
})(p.id);
/* الطيُّ يُطبَّقُ بعدَ أن تكتملَ الأقسامُ المحمَّلةُ لاحقاً */
[400, 1200, 2600].forEach(function(ms){
setTimeout(function(){ try{ window.plAccordion(box); }catch(e){} }, ms);
});
window.loadWall(p.id);
// كلانه
// كلانه: الرؤساء والنواب مو دايم مسجّلين في clan_members — نفحص الجدولين
var renderClanOf=function(cid,tag,type,points,rank){
var sk=document.getElementById('plSeekBadge'); if(sk) sk.remove(); // عنده كلان ⇒ ما يبحث عن كلان
var T=String(tag||'?');
var label = rank==='leader' ? ('👑 رئيس كلان '+T)
: rank==='vice' ? ('🎖️ نائب رئيس كلان '+T)
: rank==='coordinator' ? ('📋 منسق كلان '+T)
: ('🛡️ عضو كلان '+T);
var col = rank==='leader' ? ['rgba(255,204,0,.16)','rgba(255,204,0,.45)','var(--gold)']
: rank==='vice' ? ['rgba(56,182,255,.14)','rgba(56,182,255,.42)','var(--blue2)']
: rank==='coordinator' ? ['rgba(255,204,0,.12)','rgba(255,204,0,.4)','var(--gold)']
: ['rgba(125,142,160,.14)','rgba(125,142,160,.35)','var(--tx2)'];
var chips=document.getElementById('plChips');
var old=document.getElementById('plClanChip'); if(old) old.remove(); // ما نكرّر الشارة
if(chips) chips.insertAdjacentHTML('beforeend',
''+esc(label)+'');
var rl = rank==='leader'?'👑 رئيس':(rank==='vice'?'🎖️ نائب':(rank==='coordinator'?'📋 منسق':'🎮 لاعب'));
if($('plClan')) $('plClan').innerHTML=
'
كلانه
'+
''+
'
';
};
/* ═══════════ أصدقاؤه ═══════════
الصداقة علاقةٌ متبادلة: صفٌّ واحدٌ يخصّ الطرفين، والطرف الآخر
مرّةً في requester ومرّةً في addressee. فنجمعهما ثم نطرح صاحب
الملف. ولا نجلبها إلا للمقبولة — المعلّقة شأنُ صاحبها وحده. */
window.loadVisitors(p.id);
(function(){
var fbox=$('plFriends'); if(!fbox) return;
sb.from('friends').select('requester,addressee')
.eq('status','accepted')
.or('requester.eq.'+p.id+',addressee.eq.'+p.id)
.then(function(fr){
var rows=(fr&&fr.data)||[];
var ids=rows.map(function(f){ return f.requester===p.id?f.addressee:f.requester; })
.filter(function(x,i,a){ return x && a.indexOf(x)===i; });
if(!ids.length){ fbox.innerHTML=''; return; } /* بلا أصدقاء: لا قسمَ فارغاً */
sb.from('profiles').select('id,username,avatar_url,points,style_id')
.in('id',ids.slice(0,60)).then(function(pr2){
var L=(pr2&&pr2.data)||[];
/* الأعلى نقاطاً أوّلاً — الوجه الأول يقول شيئاً */
L.sort(function(a,b){ return (b.points||0)-(a.points||0); });
var card=function(x){
var nm=x.username||'لاعب';
var av=x.avatar_url
? ''
: ''+esc(String(nm).slice(0,2).toUpperCase())+'';
var f=window.fxOf(x.id,x.style_id);
return '';
};
fbox.innerHTML=
'
'+
'
🤝 أصدقاؤه ('+ids.length+')
'+
'
'+L.map(card).join('')+'
'+
'
';
},function(){ fbox.innerHTML=''; });
},function(){ fbox.innerHTML=''; });
})();
/* ═══ أيُّ كلانٍ يُنسب إليه؟ ═══
من يملك كلاناً وينوب في آخر كان يظهر بأيّهما اتفق، لأن الاختيار
كان «أوّل ما تجده». الترتيب هنا قاطع: ما تملكه ثم ما ترأسه ثم
ما تنوب فيه. */
sb.from('clans').select('id,tag,type,points,president_id,vice_id,created_by')
.or('created_by.eq.'+p.id+',president_id.eq.'+p.id+',vice_id.eq.'+p.id)
.neq('status','archived').then(function(cr){
var rows=cr.data||[];
/* ═══ الرئاسةُ لا تبقى بعدَ الخروج ═══
من تركَ كلانَه وانضمَّ لغيرِه كان يظهرُ رئيساً للأوّل أبداً،
لأنَّ `president_id` يبقى مكتوباً فيه. فلا تُقرأُ الرئاسةُ
إلّا لمن هو في كشفِ الكلانِ فعلاً. */
sb.from('clan_members').select('role,clan_id,clans(tag,type,points)')
.eq('profile_id',p.id).limit(1).then(function(cm){
var m=cm.data&&cm.data[0];
var myClans={};
if(m) myClans[m.clan_id]=true;
var owned=rows.filter(function(x){ return myClans[x.id]; });
var rank=function(x){ return x.created_by===p.id?1:(x.president_id===p.id?2:3); };
owned.sort(function(a,b){ return rank(a)-rank(b); });
var c=owned[0];
if(c){ renderClanOf(c.id,c.tag,c.type,c.points,
(c.created_by===p.id||c.president_id===p.id)?'leader':'vice'); return; }
if(m&&m.clans) renderClanOf(m.clan_id,m.clans.tag,m.clans.type,m.clans.points,m.role);
},function(){});
},function(){});
// سجل الفردي
sb.from('duel_results').select('*, duels(challenger_id,opponent_id,challenger_name,opponent_name)').eq('status','verified').order('id',{ascending:false}).limit(40).then(function(dr){
var all=(dr.data||[]).filter(function(x){ var d=x.duels||{}; return d.challenger_id===p.id||d.opponent_id===p.id; }).slice(0,10);
if(!$('plMatches')) return;
$('plMatches').innerHTML= all.length? all.map(function(x){
var d=x.duels||{}, won=x.winner_id===p.id;
var opp=(d.challenger_id===p.id)?d.opponent_name:d.challenger_name;
var dt=tDay(x.created_at).split('-'); var dsh=dt.length===3?(dt[2]+'/'+dt[1]):'';
return '
';
}
skHideSug();
};
window.skClearClan=function(){
if($('skClan')) $('skClan').value='';
if($('skClanQ')) { $('skClanQ').value=''; $('skClanQ').focus(); }
var pk=$('skClanPick'); if(pk){ pk.hidden=true; pk.innerHTML=''; }
skHideSug();
};
(function(){
var b=$('agFindClan');
if(b) b.addEventListener('click',function(){ go('seek'); });
var k=$('skKind');
if(k) k.addEventListener('click',function(e){ var t=e.target.closest('button'); if(!t) return;
[].forEach.call(k.querySelectorAll('button'),function(x){x.classList.remove('on');}); t.classList.add('on');
var one=t.getAttribute('data-v')==='one';
var box=$('skClanBox'); if(box) box.hidden=!one;
if(one){ setTimeout(function(){ var q=$('skClanQ'); if(q) q.focus(); },80); }
else window.skClearClan();
});
/* البحث الاستنتاجي */
var cq=$('skClanQ');
if(cq){
var ctm;
cq.addEventListener('input',function(){
if($('skClan')) $('skClan').value='';
var pk=$('skClanPick'); if(pk){ pk.hidden=true; pk.innerHTML=''; }
clearTimeout(ctm); var v=cq.value;
ctm=setTimeout(function(){ window.skSearchClans(v); },200);
});
cq.addEventListener('focus',function(){ if(cq.value.trim() && !$('skClan').value) window.skSearchClans(cq.value); });
cq.addEventListener('keydown',function(e){
var list=window._skSug||[]; if(!list.length) return;
if(e.key==='ArrowDown'||e.key==='ArrowUp'){
e.preventDefault();
window._skIdx=(window._skIdx===undefined?-1:window._skIdx)+(e.key==='ArrowDown'?1:-1);
if(window._skIdx<0) window._skIdx=list.length-1;
if(window._skIdx>=list.length) window._skIdx=0;
[].forEach.call(document.querySelectorAll('#skClanSug .sg'),function(el,i){ el.classList.toggle('on', i===window._skIdx); });
} else if(e.key==='Enter'){
e.preventDefault();
window.skPickClan(window._skIdx>=0?window._skIdx:0);
} else if(e.key==='Escape') skHideSug();
});
}
var sug=$('skClanSug');
if(sug) sug.addEventListener('click',function(e){ var t=e.target.closest('.sg'); if(!t) return; window.skPickClan(parseInt(t.getAttribute('data-i'),10)); });
document.addEventListener('click',function(e){
if(!e.target.closest || e.target.closest('#skClanBox')) return;
skHideSug();
});
var st=$('skStyle');
if(st) st.addEventListener('click',function(e){ var t=e.target.closest('button'); if(!t) return;
[].forEach.call(st.querySelectorAll('button'),function(x){x.classList.remove('on');}); t.classList.add('on');
});
var rm=$('skRemove');
if(rm) rm.addEventListener('click',function(){
if(!window._uid) return;
if(!confirm('تسحب طلبك؟ بيختفي من «يدورون كلان» ومن الديسكورد.')) return;
sb.from('profiles').update({seeking_clan:false}).eq('id',window._uid).then(function(r){
if(r&&r.error){ toast(errAr(r.error.message)); return; }
toast('🗑️ سُحب طلبك'); go('agents');
});
});
var sd=$('skSend');
if(sd) sd.addEventListener('click',function(){
var msg=$('skMsg'); if(!window._uid){ go('account'); return; }
var psn=(($('skPsn')||{}).value||'').trim();
if(psn.length<3||psn.length>24){ msg.style.color='var(--red)'; msg.textContent='🎮 آيدي السوني إجباري — من 3 إلى 24 حرفاً'; return; }
var kind=segVal('skKind','any'), clanId=(($('skClan')||{}).value||'');
if(kind==='one' && !clanId){ msg.style.color='var(--red)'; msg.textContent='اكتب تاق الكلان واختره من القائمة، أو رجّع الخيار لـ «أي كلان»'; var cqq=$('skClanQ'); if(cqq) cqq.focus(); return; }
msg.style.color='var(--gold)'; msg.textContent='...جاري النشر';
var patch={ seeking_clan:true, fa_style:segVal('skStyle','sniper'),
fa_exp:(($('skExp')||{}).value||'').trim()||null,
fa_hours:(($('skHours')||{}).value||'').trim()||null,
fa_note:(($('skNote')||{}).value||'').trim()||null,
fa_at:new Date().toISOString() };
sb.from('profiles').update(patch).eq('id',window._uid).then(function(r){
if(r&&r.error){ msg.style.color='var(--red)'; msg.textContent=errAr(r.error.message); return; }
var done=function(extra){
msg.style.color='#3ddc84'; msg.textContent='✅ نزل طلبك'+(extra||'');
setTimeout(function(){ go('agents'); window._agHighlight=window._uid; },700);
};
if(kind==='one'){
sb.from('profiles').select('username').eq('id',window._uid).single().then(function(pr){
sb.from('join_requests').insert({clan_id:clanId, user_id:window._uid,
username:(pr.data&&pr.data.username)||'لاعب', psn_id:psn,
message:(($('skNote')||{}).value||'').trim()}).then(function(ir){
if(ir.error && /duplicate|unique/i.test(ir.error.message||'')) return done(' — وعندك طلب سابق لنفس الكلان');
if(ir.error) return done(' — لكن تعذّر إرسال الطلب للكلان المحدّد');
done(' ووصل رئيس الكلان طلبك');
});
},function(){ done(''); });
} else done('');
});
});
})();
/* ===== عروض الانضمام: قائد يعرض · لاعب يرد ===== */
window.offerPlayer=function(pid,name){
var cid=window._myClanId; if(!cid){ toast('سجّل كلانك أولاً'); return; }
var msg=prompt('رسالتك إلى '+name+' (اختياري):\nمثال: نلعب يومياً 10م ونبي سنايبر','');
if(msg===null) return;
sb.rpc('send_offer',{p_clan:cid,p_player:pid,p_msg:msg||null}).then(function(r){
if(r&&r.error){
var m=String(r.error.message||'');
toast(/OFFER_EXISTS/.test(m)?'عندك عرض معلّق لهذا اللاعب'
:/ALREADY_IN_CLAN/.test(m)?'اللاعب صار بكلان'
:/CLAN_FULL/.test(m)?'كلانك مكتمل'
:/CLAN_NOT_RATIFIED/.test(m)?'كلانك بانتظار الترسيم'
:/LEADER_ONLY/.test(m)?'العروض للرئيس أو نائبه'
:(window.errAr?window.errAr(m):'تعذّر الإرسال'));
return;
}
toast('📩 أُرسل عرضك إلى '+name);
});
};
window.loadOffers=function(){
var box=$('myOffers'); if(!box||!window._uid) return;
sb.from('clan_offers').select('id,clan_id,message,offered_name,created_at')
.eq('player_id',window._uid).eq('status','pending').order('id',{ascending:false}).then(function(r){
var rows=r.data||[];
var card=$('offersCard'); if(card) card.hidden=!rows.length;
if(!rows.length){ box.innerHTML=''; return; }
sb.from('clans').select('id,tag,type,points,logo_url').in('id',rows.map(function(o){return o.clan_id;})).then(function(cr){
var cm={}; (cr.data||[]).forEach(function(c){cm[c.id]=c;});
box.innerHTML=rows.map(function(o){
var c=cm[o.clan_id]||{};
var lg=c.logo_url?'':''+esc(String(c.tag||'?').slice(0,2).toUpperCase())+'';
return '
'+
'
'+lg+
'
CLAN '+esc(String(c.tag||'؟'))+''+
'
'+(c.points||0)+' نقطة · '+(c.type==='rush'?'RASH':'SNIP')+' · من '+esc(o.offered_name||'القائد')+'
'+
(o.message?'
'+esc(o.message)+'
':'')+
'
'+
''+
''+
'
';
}).join('');
});
},function(){});
};
window.answerOffer=function(id,ok){
if(!ok){
sb.from('clan_offers').update({status:'declined',responded_at:new Date().toISOString()}).eq('id',id)
.then(function(){ toast('رُفض العرض'); window.loadOffers(); });
return;
}
sb.from('clan_offers').select('clan_id').eq('id',id).single().then(function(o){
var cid=o.data&&o.data.clan_id;
sb.rpc('accept_offer',{p_id:id}).then(function(r){
if(r&&r.error){ toast(window.errAr?window.errAr(r.error.message):'تعذّر القبول'); return; }
if(r.data==='already'){ toast('تم الرد عليه مسبقاً'); window.loadOffers(); return; }
toast('🎉 انضممت إلى '+r.data);
window.loadOffers(); if(window.refreshNow) window.refreshNow();
if(cid) window.showClanContact(cid, r.data);
else setTimeout(function(){ go('account'); },900);
});
},function(){ toast('تعذّر القبول'); });
};
/* بعد الانضمام: يسلّم اللاعبَ آيدياتِ القيادة ليضيفهم على السوني */
window.showClanContact=function(clanId, clanLabel){
var sh=$('chSheet'), bk=$('chBack'), body=$('chsBody');
if(!sh||!body){ setTimeout(function(){ go('account'); },900); return; }
bk.hidden=false; sh.hidden=false;
$('chsTitle').textContent='🎉 انضممت إلى '+(clanLabel||'كلانك');
$('chsSub').textContent='الخطوة الأخيرة — تواصل مع القيادة';
body.innerHTML='
⏳ جاري جلب بيانات القيادة...
';
sb.from('clan_members').select('role,member_name,psn,profiles(username,psn_id)')
.eq('clan_id',clanId).in('role',['leader','vice']).then(function(r){
var rows=r.data||[];
var h='
'+
'🎮 أضف قيادة كلانك على السوني '+
'التنسيق يصير بينكم مباشرة — أضفهم بآيدي السوني وابدأوا اللعب.
';
h+= rows.length ? rows.map(function(m){
var nm=(m.psn||'').trim()||(m.profiles&&(m.profiles.psn_id||'').trim())||m.member_name||(m.profiles&&m.profiles.username)||'القيادة';
var psn=(m.psn||(m.profiles&&m.profiles.psn_id)||'').trim();
var isL=m.role==='leader';
return '
'+
''+esc(nm.slice(0,2).toUpperCase())+''+
'
'+esc(nm)+''+
'
'+(isL?'👑 رئيس الكلان':'🎖️ نائب الرئيس')+'
'+
'
'+(psn?'🎮 '+esc(psn)+'':'— بلا PSN —')+'
'+
(psn?'':'')+
'
';
}).join('') : '
ما فيه رئيس أو نائب مسجّل بهذا الكلان بعد. راجع إدارة المنصة أو انتظر القيادة تتواصل معك.
طلع من CLAN '+esc(String(CM[t.from_clan]||'؟').toUpperCase())+' — لم ينضم لكلان جديد بعد
'+
'
';
}).join('') : '
لا حركات انتقال بعد 🔄 تظهر هنا عند خروج أو انتقال أي لاعب بين الكلانات
';
},function(){ box.innerHTML='
لا انتقالات بعد
'; });
});
};
window.toggleMore=function(){
if(window.closeDrawer) window.closeDrawer();
if(window.toggleUser) window.toggleUser(true);
var s=document.getElementById('moreSheet'), b=document.getElementById('moreBackdrop');
var show = s && s.hidden; if(s) s.hidden=!show; if(b) b.hidden=!show;
};
window.goMore=function(id){ window.toggleMore(); window.navTo(id); };
window.renderClansCta=function(){
var box=document.getElementById('clansCta'); if(!box) return;
if(!window._uid){ box.innerHTML=''; return; }
if(window._myClanId){ box.innerHTML=''; return; }
if(window._myMemberClanId){ box.innerHTML='
✅ أنت عضو في CLAN '+esc((window._myMemberClanTag||'').toUpperCase())+''+(window._myMemberRole==='vice'?' 🎖️ نائب':'')+
(window._myMemberRole==='leader'?'':'')+
'
'; return; }
box.innerHTML=''+
'';
};
window.openJoin=function(){
var card=$('joinCard'); if(!card) return;
// البطاقة صارت بصفحة دليل الكلانات — ننتقل لها أولاً
var sec=document.getElementById('clandir');
if(sec && !sec.classList.contains('active')) go('clandir');
sb.from('clans').select('id,tag').order('tag').then(function(r){
if($('joinClan')) $('joinClan').innerHTML=''+(r.data||[]).map(function(c){return '';}).join('');
});
card.hidden=false; setTimeout(function(){ card.scrollIntoView({behavior:'smooth',block:'center'}); },60);
};
window.submitJoin=function(){
var res=$('joinResult'); if(!res) return; if(_blocked()){ res.style.color='var(--red)'; res.textContent='🚫 حسابك محظور'; return; }
window.authUser().then(function(r){ var u=r&&r.data&&r.data.user; if(!u){ res.style.color='var(--red)'; res.textContent='سجّل دخولك أولاً'; return; }
var clanId=$('joinClan')?$('joinClan').value:''; if(!clanId){ res.style.color='var(--gold)'; res.textContent='اختر الكلان'; return; }
sb.from('profiles').select('username,psn_id').eq('id',u.id).single().then(function(pr){
var psn=(pr.data&&pr.data.psn_id)||'', uname=(pr.data&&pr.data.username)||'';
if(!psn){ res.style.color='var(--red)'; res.innerHTML='⚠️ لازم تحفظ PSN ID في حسابك أول — روح لحسابي'; return; }
res.style.color='var(--gold)'; res.textContent='...جاري الإرسال';
sb.from('join_requests').insert({clan_id:clanId, user_id:u.id, username:uname, psn_id:psn, message:(($('joinMsg')&&$('joinMsg').value)||'').trim()}).then(function(ir){
if(ir.error){ res.style.color='var(--red)'; res.textContent=/duplicate|unique/i.test(ir.error.message||'')?'عندك طلب سابق لهذا الكلان ✋':(errAr(ir.error.message)); return; }
res.style.color='#3ddc84'; res.textContent='✅ أُرسل الطلب — بيوصل رئيس الكلان إشعار'; if($('joinMsg')) $('joinMsg').value='';
});
});
});
};
window.loadJoinReqs=function(clanId){
var card=$('mgJoinCard'), box=$('mgJoinReqs'); if(!box) return;
sb.from('join_requests').select('id,user_id,username,psn_id,message').eq('clan_id',clanId).eq('status','pending').order('id',{ascending:false}).then(function(r){
/* ملخّص في مجموعة «يحتاج ردّك» أعلى الصفحة */
try{
var n=(r.data||[]).length, at=document.getElementById('mgAttention'), ar=document.getElementById('mgAttentionRows');
if(at&&ar){
at.hidden=!n;
ar.innerHTML = n ? ('') : '';
}
}catch(e){}
var rows=r.data||[];
if($('mgJoinCount')) $('mgJoinCount').textContent=rows.length?('('+rows.length+')'):'';
if(card) card.hidden = rows.length===0;
box.innerHTML = rows.map(function(j){
return '
';
}).join('');
});
};
(function(){ var b=document.getElementById('joinSend'); if(b) b.addEventListener('click',window.submitJoin); })();
window.loadClans = function(){
if(window.renderClansCta) window.renderClansCta();
var box = $('realClanList'); if(!box) return;
sb.from('clans').select('id,tag,type,status,points,wins,losses,streak,created_by,wins_casual,losses_casual')
.neq('status','archived').order('points',{ascending:false}).limit(200).then(function(r){
if(r.error){ box.innerHTML=''; return; }
window._allClans = r.data||[];
window.renderClanRanking();
});
};
window.renderClanRanking=function(){
var box=$('realClanList'); if(!box) return;
/* ═══ بطولتان لا واحدة ═══
الرشاش لعبةٌ أخرى — محاسبته بسلّم القنّاصة ظلم. كل نمطٍ سلّمُه،
وكلانٌ بلا نمطٍ محدد يظهر في الاثنين. */
var style=window._clanStyle||'sniper';
if((window._appSet||{}).hide_rush==='1') style='sniper'; /* المالك أخفى بطولة الرشاش */
var all=(window._allClans||[]).slice().filter(function(c){
if(c.status==='archived') return false;
if(c.type==='rush') return style==='rush';
if(c.type==='sniper') return style==='sniper';
return true;
});
var mode=window._clanBoard||'ranked';
/* لوحتان مستقلتان: المصنّف بالنقاط · غير المصنّف بعدد الانتصارات */
if(mode==='casual'){
all=all.filter(function(c){ return (c.wins_casual||0)+(c.losses_casual||0)>0; });
all.sort(function(a,b){
var d=(b.wins_casual||0)-(a.wins_casual||0); if(d) return d;
return (a.losses_casual||0)-(b.losses_casual||0);
});
} else {
all.sort(function(a,b){ return (b.points||0)-(a.points||0); });
}
var nb=$('boardNote');
var styleNm = style==='rush' ? '🔫 الرشاش' : '🔭 السنايبر';
if(nb) nb.innerHTML = mode==='casual'
? '🤝 غير مصنّف '+styleNm+' — بعدد الانتصارات، لا علاقة له بالنقاط.'
: '🏅 دوري '+styleNm+' الرسمي — بالنقاط من التحديات المصنّفة. لكل نمطٍ سلّمه المستقل.';
if(!all.length){ box.innerHTML='
'+
(mode==='casual'?'ما فيه مباريات غير مصنّفة موثّقة بعد 🤝':('لا كلانات '+styleNm+' مصنّفة بعد — كن أول من يعتلي هذا السلّم! 👑'))+'
'; return; }
var topN=window._clanTopN||0, q=(window._clanSearch||'').trim().toUpperCase();
var list=all.map(function(c,i){ return {c:c,rank:i+1}; });
if(topN>0) list=list.slice(0,topN);
if(q) list=list.filter(function(x){ return String(x.c.tag||'').indexOf(q)!==-1; });
if(!list.length){ box.innerHTML='
ما فيه كلان بهذا البحث 🔎
'; return; }
box.innerHTML = list.map(function(x){
var c=x.c, rank=x.rank;
var w=(mode==='casual')?(c.wins_casual||0):(c.wins||0);
var l=(mode==='casual')?(c.losses_casual||0):(c.losses||0);
var tot=w+l, ratio=tot?Math.round(w*100/tot):0;
var bigVal=(mode==='casual')?w:(c.points||0);
var bigLbl=(mode==='casual')?'فوز غير مصنّف':'نقطة';
var t=typeBadge(c.type);
var st=c.status==='ratified'?'':' · بانتظار الترسيم';
var ini=String(c.tag||'?').slice(0,2).toUpperCase();
var fire=(c.streak&&c.streak>=2)?' 🔥'+c.streak:'';
var mineB=(window._uid&&c.created_by===window._uid)?' كلاني':'';
if(rank===1){
return '
'+
'
👑
'+esc(ini)+''+
'
CLAN '+esc(c.tag)+' '+t+(fire?' '+fire+'':'')+'
🏆 المتصدّر'+mineB+'
'+
'
'+bigVal+'
'+bigLbl+'
'+
'
✅ '+w+' فوز❌ '+l+' خسارة📊 '+ratio+'% معدل الفوز
'+
'
';
}
var medal=rank===2?'🥈':rank===3?'🥉':('#'+rank);
/* شارةُ الرتبةِ محلَّ السداسيّةِ الزرقاءِ الصمّاء — الترتيبُ هو الموضعُ
الذي يُقارِنُ فيه الناسُ أنفسَهم، فالشارةُ فيه تُرى يوميّاً. */
return '
';
}).join('');
};
/* ═══ سلوك المنسدلتين ═══
الضغط على الكلمة يفتح، والاختيار يُغلق ويكتب الكلمة الجديدة —
ومنطق التبديل نفسه لم يُمسّ: الأزرار ذاتها صارت عناصر القائمة. */
(function(){
function closeAll(){
[].forEach.call(document.querySelectorAll('.dd.open'),function(d){
d.classList.remove('open');
var m=d.querySelector('.dd__menu'); if(m) m.hidden=true;
});
}
[].forEach.call(document.querySelectorAll('#ddStyle,#ddBoard'),function(d){
var pill=d.querySelector('.dd__pill'), menu=d.querySelector('.dd__menu');
if(!pill||!menu) return;
pill.addEventListener('click',function(e){
e.stopPropagation();
var was=d.classList.contains('open');
closeAll();
if(!was){ d.classList.add('open'); menu.hidden=false; }
});
menu.addEventListener('click',function(e){
var b=e.target.closest('button'); if(!b) return;
var tx=d.querySelector('.dd__tx');
if(tx) tx.textContent=b.textContent.replace('✓','').trim();
setTimeout(closeAll, 40);
});
});
document.addEventListener('click',function(e){
if(!e.target.closest || !e.target.closest('.dd')) closeAll();
});
document.addEventListener('keydown',function(e){ if(e.key==='Escape') closeAll(); });
/* السحبة تُحدّث الكلمة أيضاً */
window.ddSyncLabels=function(){
[].forEach.call(document.querySelectorAll('#ddStyle,#ddBoard'),function(d){
var on=d.querySelector('.dd__menu button.on'), tx=d.querySelector('.dd__tx');
if(on&&tx) tx.textContent=on.textContent.replace('✓','').trim();
});
};
/* ═══ إخفاء بطولة الرشاش ═══ مفتاح hide_rush من اللوحة يطوي منتقي
النمط ويعيد الجميع لسلّم السنايبر — ويسري فوراً عبر بثّ الإعدادات. */
window.applyRushVis=function(){
var off=((window._appSet||{}).hide_rush==='1');
var d=$('ddStyle'); if(d) d.style.display=off?'none':'';
var h=$('homeStyle'); if(h) h.style.display=off?'none':'';
/* ═══ يخفي الرشاش من كل منتقٍ في المنصة ═══
تسجيل الكلان · طلب التحدي · أسلوب الباحث عن كلان · فلاتر الدليل.
من كان مختاراً الرشاش يُعاد للسنايبر بنقرة حقيقية كي تتزامن حالته. */
[].forEach.call(document.querySelectorAll(
'#clType [data-v="rush"], #chPs [data-v="rush"], '+
'#skStyle [data-v="rush"], #skStyle [data-v="both"], '+
'#cdirChips [data-v="rush"], #clansChips [data-v="rush"]'
),function(b){
b.style.display = off ? 'none' : '';
if(off && b.classList.contains('on')){
var sn=b.parentNode.querySelector('[data-v="sniper"]');
if(sn){ try{ sn.click(); }catch(e){ b.classList.remove('on'); sn.classList.add('on'); } }
}
});
/* منتقي النمط في طلب التحدي يصير بلا معنى بزرٍّ واحد */
var cp=$('chPs'); if(cp) cp.style.display=off?'none':'';
var ct=$('clType'); if(ct) ct.style.display=off?'none':'';
if(!off) return;
if(window._clanStyle==='rush'){
window._clanStyle='sniper';
var stg=$('clanStyle');
if(stg) [].forEach.call(stg.querySelectorAll('button'),function(x){
x.classList.toggle('on', x.getAttribute('data-v')==='sniper');
});
if(window.ddSyncLabels) window.ddSyncLabels();
if(window.renderClanRanking) window.renderClanRanking();
}
if(window._homeStyle==='rush'){
window._homeStyle='sniper';
try{ cSet('homePodium',''); }catch(e){}
if(window.loadHome) window.loadHome();
}
};
})();
/* الرسم ثم الحركة — لا انتظار قبل المحتوى */
window.swapBoard=function(cls){
var b=$('realClanList');
if(window.renderClanRanking) window.renderClanRanking();
if(!b) return;
b.classList.remove('lin-l','lin-r','lin-f');
void b.offsetWidth;
b.classList.add(cls);
};
(function(){
var st=$('clanStyle');
if(st) st.addEventListener('click',function(e){
var b=e.target.closest('button'); if(!b) return;
var nv=b.getAttribute('data-v');
if(nv===window._clanStyle) return;
[].forEach.call(st.querySelectorAll('button'),function(x){x.classList.remove('on')});
b.classList.add('on');
window._clanStyle=nv;
/* الرشاش على اليسار فيدخل محتواه من اليسار — والعكس */
window.swapBoard(nv==='rush'?'lin-l':'lin-r');
});
var s=$('clanSearch'); if(s) s.addEventListener('input',function(){ window._clanSearch=s.value; window.renderClanRanking&&window.renderClanRanking(); });
var tf=$('clanTop'); if(tf) tf.querySelectorAll('button').forEach(function(b){ b.addEventListener('click',function(){ tf.querySelectorAll('button').forEach(function(x){x.classList.remove('on')}); b.classList.add('on'); window._clanTopN=parseInt(b.getAttribute('data-top'),10)||0; window.renderClanRanking&&window.renderClanRanking(); }); });
})();
var clTag=$('clTag');
if(clTag) clTag.addEventListener('input', function(){ $('clPrev').textContent='CLAN '+((clTag.value||'____').toUpperCase())+' | COD-AR'; });
var clType=$('clType');
if(clType) clType.querySelectorAll('button').forEach(function(b){ b.addEventListener('click', function(){
clType.querySelectorAll('button').forEach(function(x){x.classList.remove('on')}); b.classList.add('on');
});});
/* ===== قائمة لاعبي الكلان عند التسجيل ===== */
window._clMin=6;
window.loadCreateClan=function(){
/* بوابة: ما فيه تسجيل كلان قبل توثيق آيدي السوني بالحساب */
var gate=$('clPsnGate'), body=$('clBody');
sb.from('profiles').select('psn_id').eq('id',window._uid).single().then(function(pr){
var myPsn=((pr.data&&pr.data.psn_id)||'').trim();
if(!myPsn){
if(gate){ gate.hidden=false; gate.innerHTML=
'
'+
'
🔑
'+
'وثّق آيدي السوني أولاً'+
'
'+
'آيدي السوني هو هويتك بالمنصة — به يضيفك الخصم وقت التحدي، وبه يرتبط أعضاء كلانك بك. '+
'ما تقدر تسجّل كلاناً قبل حفظه.
'+
''+
'
'; }
if(body) body.hidden=true;
return;
}
if(gate){ gate.hidden=true; gate.innerHTML=''; }
if(body) body.hidden=false;
window._myPsn=myPsn;
clBuild(myPsn);
},function(){ if(body) body.hidden=false; clBuild(''); });
};
window.clBuild=function(myPsn){
getSettings(true).then(function(m){
var mn=parseInt(m.clan_min_members||'1',10); if(!mn||mn<1) mn=1;
window._clMin=mn;
var box=$('clNeedBox');
if(box){
if(mn<=1){
box.style.background='rgba(61,220,132,.06)'; box.style.borderColor='rgba(61,220,132,.3)';
box.innerHTML='✅ سجّل كلانك الآن '+
'ما فيه حد أدنى للأعضاء حالياً — سجّل ويترسّم كلانك فوراً، وأضف لاعبيك متى ما جهزوا من صفحة إدارة الكلان.';
} else {
box.style.background='rgba(255,204,0,.06)'; box.style.borderColor='rgba(255,204,0,.3)';
var rest=mn-2;
box.innerHTML='⚠️ شرط الترسيم '+
'لازم '+mn+' '+(mn===2?'لاعبَين':'لاعبين')+' على الأقل — أنت (الرئيس)'+
(mn>=2?' والنائب':'')+(rest>0?(' و'+rest+' '+(rest===1?'عضو':'أعضاء')):'')+'.'+
' ما تقدر تكمّل التسجيل قبل اكتمال العدد.';
}
}
if(!$('clRoster') || $('clRoster').children.length) return clRefresh();
clAddRow('leader', myPsn||'', myPsn||'');
if(mn>=2) clAddRow('vice','','');
for(var i=2;i'+
(lead?'👑 رئيس الكلان (أنت)':vice?'🎖️ نائب الرئيس':'🎮 لاعب '+(i-1))+''+
''+
(lead?'':'')+
''+
''+
'';
box.appendChild(d);
}
function clRows(){
/* آيدي السوني هو الهوية والاسم معاً — خانة واحدة */
return [].slice.call(document.querySelectorAll('#clRoster .clrow')).map(function(d){
var f=d.querySelector('.clpsn');
/* ترقيم آيفون «الذكي» يصل هنا شرطةً طويلة — يُردّ لأصله قبل الفحص */
var psn=window.fixPsn(f.value);
if(psn!==f.value) f.value=psn;
return { role:d.getAttribute('data-role'), name:psn, psn:psn, el:d };
});
}
function clRefresh(){
var rows=clRows(), need=window._clMin;
var ok=rows.filter(function(r){ return r.psn.length>=3 && r.psn.length<=24; });
// تكرار PSN
var seen={}, dup=false;
ok.forEach(function(r){ var k=r.psn.toLowerCase(); seen[k]=(seen[k]||0)+1; if(seen[k]>1) dup=true; });
rows.forEach(function(r){
var el=r.el.querySelector('.clpsn');
var bad = r.psn && (r.psn.length<3 || r.psn.length>24 || seen[r.psn.toLowerCase()]>1);
el.style.borderColor = bad ? 'rgba(255,107,94,.6)' : '';
var h=r.el.querySelector('.clname'); if(h) h.value=r.psn;
});
var c=$('clCount'); if(c){ c.textContent='('+ok.length+'/'+need+')'; c.style.color=ok.length>=need?'#3ddc84':'var(--tx3)'; }
var p=$('clProgress');
if(p){
var pct=Math.min(100,Math.round(ok.length*100/need));
var left=Math.max(0,need-ok.length);
var bar = need>1 ? '
'+
'
' : '';
p.innerHTML=bar+
'
'+
(dup?'⚠️ فيه آيدي سوني مكرر — كل لاعب بآيدي مختلف'
: ok.length>=need?(need>1?'✅ القائمة مكتملة — جاهز للترسيم':'✅ جاهز — سجّل كلانك')
: (need===1?'اكتب اسمك وآيدي السوني حقك'
: ('باقي '+left+' '+(left===1?'لاعب واحد باسمه وآيديه':'لاعبين بأسمائهم وآيدياتهم'))))+'
';
}
var btn=$('btnCreateClan');
if(btn){ var good=ok.length>=need && !dup; btn.disabled=!good; btn.style.opacity=good?'':'.45'; btn.style.cursor=good?'':'not-allowed'; }
}
(function(){
var box=$('clRoster');
if(box){
box.addEventListener('input',clRefresh);
box.addEventListener('click',function(e){
var b=e.target.closest('.clrm'); if(!b) return;
var row=b.closest('.clrow'); if(row) row.remove(); clRefresh();
});
}
var add=$('clAddRow');
if(add) add.addEventListener('click',function(){ clAddRow('member','',''); clRefresh(); });
})();
function clErrAr(m){
m=String(m||'');
if(/TAG_TAKEN/.test(m)) return '⚠️ التاق مستخدم من كلان آخر — اختر غيره';
if(/TAG_INVALID/.test(m)) return '⚠️ التاق حروف إنجليزية/أرقام فقط — من 1 إلى 4';
if(/ALREADY_IN_CLAN/.test(m)) return '⚠️ أنت عضو في كلان — اخرج منه أولاً';
if(/ALREADY_OWNER/.test(m)) return '⚠️ عندك كلان مسجّل بالفعل';
if(/PSN_PROFILE_REQUIRED/.test(m)) return '🔑 وثّق آيدي السوني في حسابك أولاً — بدونه ما تقدر تسجّل كلاناً';
if(/ROSTER_SHORT:(\d+)/.test(m)) return '⚠️ لازم '+m.match(/ROSTER_SHORT:(\d+)/)[1]+' لاعبين على الأقل';
if(/NAME_MISSING:(\d+)/.test(m)) return '⚠️ اسم اللاعب رقم '+m.match(/NAME_MISSING:(\d+)/)[1]+' فاضي';
if(/PSN_INVALID:(\d+)/.test(m)) return '⚠️ آيدي السوني للاعب رقم '+m.match(/PSN_INVALID:(\d+)/)[1]+' غير صالح (3–24 حرفاً)';
if(/PSN_DUPLICATE:(.+)/.test(m)) return '⚠️ آيدي مكرر بالقائمة: '+m.match(/PSN_DUPLICATE:(.+)/)[1];
if(/PSN_IN_CLAN:(.+)/.test(m)) return '⚠️ الآيدي '+m.match(/PSN_IN_CLAN:(.+)/)[1]+' مسجّل في كلان آخر — لا يُضاف لكلانين';
if(/NAME_IN_CLAN:(.+)/.test(m)) return '⚠️ الآيدي '+m.match(/NAME_IN_CLAN:(.+)/)[1]+' مستخدم باسم لاعب في كلان آخر';
if(/MEMBER_TAKEN:(.+)/.test(m)) return '⚠️ الآيدي '+m.match(/MEMBER_TAKEN:(.+)/)[1]+' مأخوذ في كلان آخر — غيّره أو اطلب من صاحبه يخرج';
if(/HAS_ARCHIVED_CLAN/.test(m)) return '🔒 عندك كلان بالأرشيف — استرجعه أو احذفه قبل ما تسجّل غيره';
if(/CLAN_REG_CLOSED/.test(m)) return '🔒 تسجيل الكلانات مقفل حالياً من الإدارة';
return window.errAr?window.errAr(m):m;
}
if($('btnCreateClan')) $('btnCreateClan').addEventListener('click', function(){
var msg=$('clMsg');
window.authUser().then(function(res){
var user = res && res.data && res.data.user;
if(!user){ msg.textContent='سجّل دخولك أولاً (تبويب حسابي)'; go('account'); return; }
var tag=(($('clTag').value||'').trim()).toUpperCase();
if(!/^[A-Z0-9]{1,4}$/.test(tag)){ msg.style.color='var(--red)'; msg.textContent='⚠️ اكتب تاق الكلان — حروف إنجليزية/أرقام، من 1 إلى 4'; $('clTag').focus(); return; }
var rows=clRows().filter(function(r){ return r.psn.length>=3; });
if(rows.length بـ '+(d.members||rows.length)+' لاعبين — معتمد رسمياً وجاهز للتحديات.';
/* الصلاحيات مخزّنة من قبل تسجيل الكلان — نُحدّثها فوراً
كي يجد زر «طلب تحدي» فعّالاً بلا إعادة تحميل الصفحة */
if(window.refreshNow) window.refreshNow();
if(window.applyPerms) window.applyPerms();
setTimeout(function(){ if(window.applyPerms) window.applyPerms(); }, 1200);
setTimeout(function(){ go('manageclan'); }, 1400);
},function(){ $('btnCreateClan').disabled=false; msg.style.color='var(--red)'; msg.textContent='تعذّر التسجيل — حاول مرة ثانية'; });
});
});
// ===== التحديات (حقيقية + إشعارات حيّة) =====
var CH_STATUS={open:'مفتوح',pending:'بانتظار القبول',accepted:'مقبول',live:'جارٍ',completed:'مكتمل',cancelled:'ملغى',expired:'انتهت المدة',postponed:'مؤجَّل',postpone_requested:'طُلب تأجيله',resume_requested:'طُلب استئنافه'};
var CH_COLOR={open:'#38b6ff',pending:'#38b6ff',accepted:'#3ddc84',live:'#3ddc84',completed:'#7d8ea0',expired:'#7d8ea0',cancelled:'#ff6b5e',postponed:'#ffaa00',postpone_requested:'#ffaa00',resume_requested:'#38b6ff'};
// شارة الحالة: كلمة نظيفة بلون CSS — بدون إيموجي
var CH_EN={open:'OPEN',pending:'PENDING',accepted:'LIVE',live:'LIVE',completed:'ENDED',expired:'EXPIRED',cancelled:'CANCELLED',postponed:'POSTPONED',postpone_requested:'PAUSED',resume_requested:'RESUMING'};
function chStatusChip(s){
var c=CH_COLOR[s]||'#7d8ea0', txt=CH_STATUS[s]||s, en=CH_EN[s]||'';
var pulse=(s==='accepted'||s==='live')?'':'';
return ''+pulse+txt+
(en?''+en+'':'')+'';
}
function typeBadge(type){ return type==='rush'
? 'RASH'
: 'SNIP'; }
/* العنوان اسم الكلانين فقط — شارة النمط مكانها سطر المعلومات تحت */
function chClan(id){ var c=window._clanMap&&window._clanMap[id]; if(!c) return 'أي كلان'; return 'CLAN '+esc(String(c.tag||''))+''; }
/* شارة مصنّف / غير مصنّف */
function rankBadge(ranked){
var on = ranked!==false;
return ''+(on?'🏅 مصنّف':'🤝 غير مصنّف')+'';
}
window.rankBadge=rankBadge;
function chContact(cid){ var cc=window._clanContacts||{}, x=cc[cid]||{}, out=[];
/* الاسم في الكشف هو آيدي السوني غالباً — فلا نكرّره مرتين */
var line=function(ic,dflt,p){
var nm=String((p&&p.name)||'').trim(), ps=String((p&&p.psn)||'').trim();
if(ps && nm.toLowerCase()===ps.toLowerCase()) return ic+' '+esc(ps)+'';
if(!ps) return ic+' '+esc(nm||dflt);
return ic+' '+esc(nm||dflt)+' — '+esc(ps)+'';
};
if(x.leader&&(x.leader.psn||x.leader.name)) out.push(line('👑','الرئيس',x.leader));
if(x.vice&&(x.vice.psn||x.vice.name)) out.push(line('🎖️','النائب',x.vice));
return out.length?out.join(' '):'لا رئيس/نائب مسجّل';
}
/* عدّاد المهلة: ثوانٍ تحت الدقيقة، وبعدها د:ثث */
function fmtLeft(sec){
sec=Math.max(0,sec|0);
if(sec<60) return sec+'ث';
return Math.floor(sec/60)+':'+('0'+(sec%60)).slice(-2)+' د';
}
function renderCh(box,rows,kind){
if(!box) return;
if(!rows.length){ box.innerHTML='
'+(kind==='mine'?'ما عندك تحدي قائم — أطلق واحد من فوق ⚡':'لا يوجد حالياً')+'
'; return; }
var _h3690=rows.map(function(c){
var vs=kind==='open'?'يطلب':'VS';
var meta=chStatusChip(c.status)+' '+typeBadge(c.playstyle==='rush'?'rush':'sniper')+' '+rankBadge(c.ranked);
var btns='';
/* الطلب المفتوح لا يُحسم من طرف واحد: تتقدّم، وصاحبه يختار خصمه */
if(kind==='open') btns=' ';
if(kind==='directed') btns=' ';
if(kind==='mine'){
/* التحدي القائم: الخروج منه «انسحاب» بعواقبه، لا مجرد إلغاء طلب */
var live=(c.status==='accepted'||c.status==='live'||c.status==='postponed'||c.status==='postpone_requested'||c.status==='resume_requested');
btns='';
/* البلاغُ يظهرُ حيثُ توجدُ غرفة — والقاعدةُ وحدَها تقرّرُ متى يُقبَل،
فالزرُّ يسألُ قبلَ أن يرسل. */
if(live||c.status==='postponed') btns+=' ';
}
var note='';
var _hasRoom=(c.status==='accepted'||c.status==='live'||c.status==='postponed'||c.status==='postpone_requested'||c.status==='resume_requested');
if(_hasRoom && window._myIds){
var mineId=(window._myIds.indexOf(c.challenger_clan)!==-1)?c.challenger_clan:c.opponent_clan;
var oppId2=(mineId===c.challenger_clan)?c.opponent_clan:c.challenger_clan;
var mm=window._clanMap||{}; var tm=tDT(c.created_at);
note='';
var roomLabel=c.status==='postponed'?'⏸️ الغرفة (مؤجّل)':'📂 افتح التحدي';
btns = ''+btns;
}
var left='';
if(c.status==='open'||c.status==='pending'){
var rem=Math.max(0, window.chTTL(c) - Math.floor((Date.now()-new Date(c.created_at).getTime())/1000));
left=' · ⏳ '+fmtLeft(rem)+'';
}
var bc=CH_COLOR[c.status]||'#7d8ea0';
/* العنوان نفسه مدخل التحدي — قابل يُضغط */
var openFn = _hasRoom ? ('window.openChChat(\''+c.id+'\')') : ('window.goChallenge(\''+c.id+'\')');
return '
';
/* الجدول فيه score_a و score_b — لا عمود score */
/* من السجل الدائم — يبقى حتى لو انحذف الكلان أو التحدي */
Promise.all([
sb.from('match_log').select('*').order('verified_at',{ascending:false}).limit(400),
sb.from('clans').select('id,tag,name,logo_url,status,points,wins,losses,recruiting')
]).then(function(rs){
if(rs[0].error){ box.innerHTML='
تعذّر تحميل السجل
'; return; }
var cm={}; (rs[1].data||[]).forEach(function(c){ cm[c.id]=c; }); window._histClans=cm;
var out=(rs[0].data||[]).map(function(m){ return window.matchNorm(m, cm); });
window._hist.rows=out; window._hist.limit=25;
window.renderHistory();
},function(){ box.innerHTML='
تعذّر تحميل السجل
'; });
};
/* ═══ البحث يجد الصفحات لا المباريات فقط ═══
من يبحث عن كلان يريد صفحته، لا سطر مباراة واحدة خاضها. */
window._histPeople=null;
function histPeople(){
if(window._histPeople) return Promise.resolve(window._histPeople);
if(window._allPlayers && window._allPlayers.length){ window._histPeople=window._allPlayers; return Promise.resolve(window._histPeople); }
return sb.from('profiles').select('id,username,psn_id,avatar_url,points,wins,losses,seeking_clan')
.order('points',{ascending:false}).limit(300).then(function(r){
window._histPeople=(r.data||[]).filter(function(p){ return p.username; });
return window._histPeople;
},function(){ window._histPeople=[]; return []; });
}
window.renderHistEntities=function(){
var box=$('hEntities'); if(!box) return;
var q=(window._hist.q||'').trim().toLowerCase();
var fld=window._hist.field||'all', f=window._hist.f||'all';
if(!q || fld==='date' || fld==='score'){ box.innerHTML=''; return; }
var wantClans =(fld==='all'||fld==='clan') && (f==='all'||f==='team');
var wantPeople=(fld==='all'||fld==='player') && (f==='all'||f==='duel');
var cm=window._histClans||{};
var clans=!wantClans?[]:Object.keys(cm).map(function(k){return cm[k];}).filter(function(c){
return ((String(c.tag||'')+' '+String(c.name||'')).toLowerCase()).indexOf(q)!==-1;
}).slice(0,6);
var paint=function(people){
var cards='';
clans.forEach(function(c){
var tg=String(c.tag||'؟');
var lg=c.logo_url
? ''
: ''+esc(tg.slice(0,4))+'';
var t=(c.wins||0)+(c.losses||0);
cards+='
'
: '';
};
if(!wantPeople) return paint([]);
histPeople().then(function(ppl){
paint((ppl||[]).filter(function(p){
return ((String(p.username||'')+' '+String(p.psn_id||'')).toLowerCase()).indexOf(q)!==-1;
}).slice(0,6));
});
};
/* ═══════════ بطاقة النتيجة ═══════════
بطاقةٌ واحدة يستعملها السجل العام والرئيسية، فلا يفترق شكلُهما أبداً.
ورقمُ كل فريق يُرسم في جهته: الصفّ عربيّ، فأول عنصرٍ فيه إلى اليمين
— والنتيجة كانت تُكتب بـltr داخله فينقلب رقمُ كلٍّ إلى جهة خصمه. */
window.matchCard=function(r){
var tm=tHM(r.t);
var side=function(name,win,logo,id,kind){
var ini=String(name).replace(/^CLAN /,'').slice(0,2).toUpperCase();
var av=logo?''
:''+esc(ini)+'';
var click=id?(kind==='team'?('openClan(\''+id+'\')'):('openPlayer(\''+id+'\')')):'';
return '
'+av+
''+esc(name)+'
';
};
var mm=String(r.score||'').match(/^\s*(\d+)\s*-\s*(\d+)\s*$/);
var num=function(v,win){ return ''+v+''; };
var score = mm
? '
'+
(q ? (($('hEntities')&&$('hEntities').innerHTML)
? 'ما خاض مباريات موثّقة بعد 📭 لكن صفحته فوق ↑'
: ('ما لقيت نتيجة لـ «'+esc(window._hist.q)+'» 🔎 جرّب تاق الكلان أو اسم اللاعب'))
: (all.length ? 'ما فيه مباريات بهذا الفلتر 📭'
: 'ما فيه مباريات موثّقة بعد 📭 أول نتيجة توثَّق بتظهر هنا فوراً'))+'
';
if($('hMore')) $('hMore').hidden=true; return; }
var shown=list.slice(0, window._hist.limit);
// تجميع حسب اليوم
var html='', lastDay='';
var AR=['الأحد','الإثنين','الثلاثاء','الأربعاء','الخميس','الجمعة','السبت'];
shown.forEach(function(r){
var d=new Date(r.t||Date.now());
var day=tDay(r.t);
if(day!==lastDay){
lastDay=day;
var lbl = day===tDay(new Date()) ? 'اليوم'
: day===tDay(new Date(Date.now()-86400000)) ? 'أمس'
: AR[tWD(day)]+' · '+day.split('-').reverse().join('/');
html+='
'+lbl+'
';
}
html+=window.matchCard(r);
});
box.innerHTML=html;
if($('hMore')) $('hMore').hidden = (shown.length>=list.length);
};
(function(){
var s=$('hSearch'); if(s) s.addEventListener('input',function(){ window._hist.q=s.value; window._hist.limit=25; window.renderHistory(); });
var fd=$('hField'); if(fd) fd.addEventListener('change',function(){
window._hist.field=fd.value; window._hist.limit=25;
var PH={all:'🔎 اكتب ما تبحث عنه...',clan:'🔎 مثال: PWRZ',player:'🔎 اسم اللاعب',
date:'🔎 مثال: 2026-08-01',score:'🔎 مثال: 4 - 2'};
if(s) s.placeholder=PH[fd.value]||PH.all;
window.renderHistory();
});
var f=$('hFilter'); if(f) f.addEventListener('click',function(e){ var b=e.target.closest('button'); if(!b) return;
[].forEach.call(f.querySelectorAll('button'),function(x){x.classList.remove('on');}); b.classList.add('on');
window._hist.f=b.getAttribute('data-v'); window._hist.limit=25; window.renderHistory(); });
var m=$('hMore'); if(m) m.addEventListener('click',function(){ window._hist.limit+=25; window.renderHistory(); });
})();
function loadChLists(user){
sb.from('clans').select('id,tag,type,created_by').then(function(cr){
var clans=cr.data||[]; var map={}; clans.forEach(function(c){map[c.id]=c;}); window._clanMap=map;
sb.from('clan_members').select('clan_id,role,member_name,psn,profiles(username,psn_id)').in('role',['leader','vice']).then(function(mr){
var cc={}; (mr.data||[]).forEach(function(m){ var ps=(m.psn||(m.profiles&&m.profiles.psn_id)||'').trim(); cc[m.clan_id]=cc[m.clan_id]||{}; cc[m.clan_id][m.role]={name:(ps||m.member_name||(m.profiles&&m.profiles.username)||''), psn:ps}; }); window._clanContacts=cc;
if(!window._contactsReadyOnce){ window._contactsReadyOnce=true; loadChLists(user); }
},function(){});
var myIds=user?clans.filter(function(c){return c.created_by===user.id;}).map(function(c){return c.id;}):[];
/* كلان النائب/الرئيس بالعضوية يُحسب «كلاني» أيضاً — فيرى تحدياته
وخاناته ويوثّق نتائجه، لا أن يظهر له الكشف فارغاً */
if(window._myMemberClanId && (window._myMemberRole==='leader'||window._myMemberRole==='vice'||window._myMemberRole==='coordinator')
&& myIds.indexOf(window._myMemberClanId)===-1) myIds.push(window._myMemberClanId);
window._myIds=myIds;
if($('rsChallenge')){
if(myIds.length){
sb.from('challenges').select('id,challenger_clan,opponent_clan').in('status',['accepted','live']).then(function(r){
var rows=(r.data||[]).filter(function(c){return myIds.indexOf(c.challenger_clan)!==-1||myIds.indexOf(c.opponent_clan)!==-1;});
window._myChallenges={}; var opts='';
rows.forEach(function(c){ window._myChallenges[c.id]=c; var a=(map[c.challenger_clan]||{}).tag||'؟', b=(map[c.opponent_clan]||{}).tag||'؟'; opts+=''; });
$('rsChallenge').innerHTML=opts;
/* المربع ما يظهر إلا لمن عنده تحدٍّ مقبول فعلاً */
if($('rsCard')) $('rsCard').hidden = !rows.length;
});
} else { $('rsChallenge').innerHTML=''; if($('rsCard')) $('rsCard').hidden=true; }
}
if($('rpChallenge') && myIds.length){
sb.from('challenges').select('id,challenger_clan,opponent_clan').or('challenger_clan.in.('+myIds.join(',')+'),opponent_clan.in.('+myIds.join(',')+')').order('created_at',{ascending:false}).limit(20).then(function(r){
var opts=''; window._allChMap={};
(r.data||[]).forEach(function(c){ window._allChMap[c.id]=c; var a=(map[c.challenger_clan]||{}).tag||'؟', b=(map[c.opponent_clan]||{}).tag||'أي كلان'; opts+=''; });
if($('rpChallenge')) $('rpChallenge').innerHTML=opts;
},function(){});
}
sb.from('challenges').select('*').is('opponent_clan',null).eq('status','open').order('created_at',{ascending:false}).limit(40).then(function(r){
renderCh($('chOpen'),(r.data||[]).filter(function(c){return myIds.indexOf(c.challenger_clan)===-1;}),'open');
});
if(myIds.length){
sb.from('challenges').select('*').in('opponent_clan',myIds).eq('status','pending').order('created_at',{ascending:false}).then(function(r){ renderCh($('chDirected'),r.data||[],'directed'); });
var f=window._chFilter||'active';
var sts = f==='active'?['open','pending','accepted','live','postponed','postpone_requested','resume_requested'] : f==='done'?['completed','expired'] : f==='cancelled'?['cancelled'] : ['open','pending','accepted','live','postponed','postpone_requested','resume_requested','completed','expired','cancelled'];
sb.from('challenges').select('*').or('challenger_clan.in.('+myIds.join(',')+'),opponent_clan.in.('+myIds.join(',')+')').in('status',sts).order('created_at',{ascending:false}).limit(40).then(function(r){
/* الطلب الموجَّه لكلانك مكانه «📥 طلبات موجّهة لك» حتى تقبله —
بعد القبول ينتقل إلى «تحدياتي». لا يظهر بالقائمتين معاً. */
var rows=(r.data||[]).filter(function(c){
var iAmOpp = myIds.indexOf(c.opponent_clan)!==-1;
var iAmChl = myIds.indexOf(c.challenger_clan)!==-1;
return !(c.status==='pending' && iAmOpp && !iAmChl);
});
renderCh($('chMine'),rows,'mine');
});
} else { renderCh($('chDirected'),[],'directed'); renderCh($('chMine'),[],'mine'); }
if(window.loadBids) window.loadBids(myIds);
if(window.renderSlots) window.renderSlots(myIds[0]);
/* الشارات تتجدّد مع كل إنعاش — فيظهر الجديد بلا فتح القسم */
if(window.chCountLive) window.chCountLive();
if(window.chCountOpen) window.chCountOpen();
/* لوحة الجارية معروضة الآن؟ نُحدّثها كي تختفي المنتهية وتظهر الجديدة */
var lv=document.getElementById('secLive');
if(lv && !lv.classList.contains('fhide') && window.loadLiveChallenges) window.loadLiveChallenges();
});
}
window.submitReport=function(){
var msg=$('rpMsg'); if(!msg) return; if(_blocked()){ msg.style.color='var(--red)'; msg.textContent='🚫 حسابك محظور'; return; }
window.authUser().then(function(res){ var u=res&&res.data&&res.data.user; if(!u){ msg.style.color='var(--red)'; msg.textContent='سجّل دخولك'; go('account'); return; }
var chId=$('rpChallenge')?$('rpChallenge').value:''; if(!chId){ msg.style.color='var(--gold)'; msg.textContent='اختر التحدي'; return; }
var reason=($('rpReason')&&$('rpReason').value)||'أخرى'; var details=(($('rpDetails')&&$('rpDetails').value)||'').trim();
/* ═══ البلاغُ يصلُ كاملاً أو لا يصل ═══
كان يُرسَلُ بلا تفاصيلَ وبلا اسمِ المشكوِّ منه، فتصلُ المشرفَ بطاقةٌ
فيها سببٌ واحدٌ لا يُحكَمُ به: «حرق» — من؟ في أيِّ راوند؟ فيسألُ
ويُطيلُ، أو يردُّ البلاغَ ظلماً. فالتفاصيلُ شرطٌ الآن، والمشكوُّ منه
يُستخرَجُ من التحدي نفسِه لا من كتابةِ المُبلِّغ. */
if(details.length<10){ msg.style.color='var(--gold)'; msg.textContent='اكتب تفاصيل المخالفة — وضّح وش صار وفي أي راوند'; if($('rpDetails')) $('rpDetails').focus(); return; }
var accused='';
try{
var _c=(window._allChMap||{})[chId] || ((window._cc&&window._cc.id===chId)?(window._cc.ch||{}):{}) || {};
var _mm=window._clanMap||{}, _mine=window._myIds||[];
var _other=(_mine.indexOf(_c.challenger_clan)!==-1)?_c.opponent_clan:_c.challenger_clan;
var _tag=String((_mm[_other]||{}).tag||'').toUpperCase();
if(_tag) accused='CLAN '+_tag;
}catch(e){}
var evidence=(window._rp||[]).join(',');
msg.style.color='var(--gold)'; msg.textContent='...إرسال';
sb.from('reports').insert({reporter_id:u.id, challenge_id:chId, accused:accused||null, reason:reason, details:details, evidence_url:evidence, status:'open'}).then(function(r){
if(r.error){ msg.style.color='var(--red)'; msg.textContent=errAr(r.error.message); return; }
msg.style.color='#3ddc84'; msg.textContent='✅ أُرسل البلاغ — طاقم المشرفين بيرد عليك بأسرع وقت 🛡️'; if($('rpDetails'))$('rpDetails').value=''; window._rp=[]; window.paintRpList(); if($('rpUpMsg'))$('rpUpMsg').textContent='';
});
});
};
/* ═══ أدلة البلاغ ═══
البلاغ الواحد يحمل حتى ٨ أدلة: صور ومقاطع معاً — لأن المخالفة
الواحدة قد تحتاج لقطة تُظهر السلاح ومقطعاً يُظهر إطالة الزوم. */
window._rp=[];
window.paintRpList=function(){
var l=$('rpList'), b=$('rpFileBtn');
if(b) b.textContent='📎 أرفق أدلة — صور ومقاطع ('+window._rp.length+'/8)';
if(!l) return;
l.innerHTML=window._rp.map(function(u,i){
var v=window.isVid(u);
return ''+
''+
(v ? ''+
'▶'
: '')+
''+
'';
}).join('');
};
window.uploadReportProof=function(){
var fi=$('rpFile'), msg=$('rpUpMsg');
if(!fi||!fi.files||!fi.files.length||!msg) return;
/* ═══ بالتوازي لا واحداً بعدَ واحد ═══
كانت الأدلّةُ ترتفعُ بالتتابع: ثمانيةُ ملفّاتٍ = ثمانيةُ أضعافِ
الانتظار، ووصلةُ الجوّالِ عاطلةٌ بينَ الواحدِ والذي يليه. ثلاثةٌ
معاً تملأُ الوصلةَ ولا تُغرِقُ خادماً بنواتَين. */
var files=[].slice.call(fi.files).slice(0, Math.max(0, 8-window._rp.length));
var over=fi.files.length-files.length;
var rpc=$('rpChallenge')?$('rpChallenge').value:'';
var okN=0, failed=[], total=files.length, prog={}, LANES=3;
if(over>0) failed.push('الحد ٨ أدلة — تُرك '+over);
if(!total){ fi.value=''; msg.style.color='var(--gold)'; msg.textContent='⚠️ '+failed.join(' · '); return; }
var paint=function(){
var pc=0; for(var k in prog) pc+=prog[k];
msg.style.color='var(--tx3)';
msg.textContent='⏳ نرفع '+total+' — '+Math.round(pc/total*100)+'%';
};
var finish=function(){
fi.value=''; window.paintRpList();
if(failed.length){ msg.style.color='var(--gold)';
msg.textContent='⚠️ '+failed.join(' · ')+(okN?(' — أُضيف '+okN):''); }
else { msg.style.color='#3ddc84'; msg.textContent='✅ أُضيفت الأدلة ('+window._rp.length+'/8)'; }
};
var next=0, live=0, doneN=0;
var pump=function(){
while(live(window.UP_MAX_MB||100)*1024*1024){
failed.push((f.name||'ملف')+': أكبر من 15MB');
prog[i]=1; live--; doneN++; if(doneN===total) finish(); else pump();
return;
}
window.upProof(f, rpc||'report', function(p){ prog[i]=p; paint(); })
.then(function(d){
if(d&&d.ok){ window._rp.push(d.url); okN++; window.paintRpList(); }
else failed.push((f.name||'ملف')+': '+((d&&d.why)||'تعذّر'));
},function(){ failed.push((f.name||'ملف')+': تعذّر الرفع'); })
.then(function(){
prog[i]=1; live--; doneN++;
if(doneN===total) finish(); else pump();
});
})(files[next], next); next++;
}
paint();
};
pump();
};
document.addEventListener('click',function(e){
var t=e.target&&e.target.closest?e.target.closest('[data-rpdel]'):null; if(!t) return;
e.preventDefault(); e.stopPropagation();
window._rp.splice(parseInt(t.getAttribute('data-rpdel'),10),1); window.paintRpList();
});
window.goChallenges=function(f){
window._chFilter='active';
/* «جارية الآن» من الرئيسية/التنقّل تفتح لوحة الجارية العامّة مباشرةً */
window._chOpenTab=(f==='active')?'live':(f==='open'?'open':null);
go('challenges');
};
/* ================= بطاقة التحدي — تُفتح من أي مكان ================= */
/* ═══ البطاقةُ تُفتَحُ فوقَ شاشتِها لا فوقَ أيِّ شاشة ═══
كانت تُفتَحُ حيثُ أنت، فمن ضغطَ إشعارَ «تحدٍّ مفتوح» وهو في الرئيسيّة
رآها معلَّقةً فوقَها — وإذا أغلقَها رجعَ إلى الرئيسيّةِ لا إلى قائمةِ
التحدّيات، فبدا وكأنَّ شيئاً ظهرَ من تلقاءِ نفسِه. الآن ننتقلُ أوّلاً
ثمَّ نفتح، فيجدُ خلفَها ما يتوقّعُه ويجدُه أمامَه حينَ يُغلِق. */
window._goChalOn=function(id){
if(!id) return;
var cs=document.getElementById('challenges');
if(cs && cs.classList.contains('active')){ return window.goChallenge(id); }
try{ go('challenges'); }catch(e){}
setTimeout(function(){ window.goChallenge(id); }, 140);
};
window.closeChSheet=function(){ var s=$('chSheet'), b=$('chBack'); if(s) s.hidden=true; if(b) b.hidden=true; if(window._chsTimer){clearInterval(window._chsTimer); window._chsTimer=null;} };
var CH_TXT={open:'مفتوح — من يقبل؟',pending:'موجّه لكلان',accepted:'مقبول — جهّزوا',live:'جارٍ الآن',completed:'منتهٍ',expired:'انتهت مدته',cancelled:'ملغى'};
/* يفتح تحدياً بعينه ببطاقة مستقلة فيها كل التفاصيل وأزرار التنفيذ */
window.goChallenge=function(id){
if(!id) return go('challenges');
var sh=$('chSheet'), bk=$('chBack'), body=$('chsBody');
if(!sh||!body) return go('challenges');
bk.hidden=false; sh.hidden=false;
$('chsTitle').textContent='تحدٍّ'; $('chsSub').textContent='...جاري التحميل';
body.innerHTML='
';
}
function renderChSheet(c,map,mineIds,ownedClan){
var body=$('chsBody'); if(!body) return;
var A=map[c.challenger_clan], B=map[c.opponent_clan];
$('chsTitle').innerHTML='CLAN '+esc(String((A||{}).tag||'؟'))+' ضد '+(B?'CLAN '+esc(String(B.tag||'')):'أي كلان');
$('chsSub').textContent=(CH_TXT[c.status]||c.status)+' · '+tDT(c.created_at);
var iAmChallenger = mineIds.indexOf(c.challenger_clan)!==-1;
var iAmOpponent = c.opponent_clan && mineIds.indexOf(c.opponent_clan)!==-1;
var isOpen = !c.opponent_clan && c.status==='open';
var h='';
h+='
'; return; }
var rows=r.data||[];
if(!rows.length){ window._cdir.rows=[]; return window.renderClanDir(); }
// عدد الأعضاء لكل كلان
sb.from('clan_members').select('clan_id').then(function(mr){
var cnt={}; (mr.data||[]).forEach(function(m){ cnt[m.clan_id]=(cnt[m.clan_id]||0)+1; });
rows.forEach(function(c){ c._n=cnt[c.id]||0; });
window._cdir.rows=rows; window.renderClanDir();
},function(){ window._cdir.rows=rows; window.renderClanDir(); });
},function(){ box.innerHTML='
تعذّر التحميل
'; });
};
/* خلط ثابت ما دام المستخدم بالصفحة — ما يتغيّر مع كل إعادة رسم */
function seededShuffle(arr,seed){
var a=arr.slice(), s=seed;
for(var i=a.length-1;i>0;i--){
s=(s*9301+49297)%233280;
var j=Math.floor((s/233280)*(i+1));
var t=a[i]; a[i]=a[j]; a[j]=t;
}
return a;
}
function agoAr(iso){
if(!iso) return '—';
var d=Math.floor((Date.now()-new Date(iso).getTime())/1000);
if(d<60) return 'الآن';
if(d<3600) return 'قبل '+Math.floor(d/60)+' دقيقة';
if(d<86400) return 'قبل '+Math.floor(d/3600)+' ساعة';
if(d<2592000) return 'قبل '+Math.floor(d/86400)+' يوم';
return tDay(iso);
}
window.renderClanDir=function(){
var box=$('cdirList'); if(!box) return;
var C=window._cdir, rows=(C.rows||[]).slice();
var q=(C.q||'').trim().toUpperCase();
if(q) rows=rows.filter(function(c){
return String(c.tag||'').indexOf(q)!==-1 || String(c.name||'').toUpperCase().indexOf(q)!==-1;
});
if(C.filter==='open') rows=rows.filter(function(c){ return c.recruiting!==false; });
if(C.filter==='ratified') rows=rows.filter(function(c){ return c.status==='ratified'; });
if(C.filter==='forming') rows=rows.filter(function(c){ return c.status!=='ratified'; });
if(C.sort==='random') rows=seededShuffle(rows, Math.floor(C.seed*233280));
else if(C.sort==='active') rows.sort(function(a,b){ return String(b.activity_at||'').localeCompare(String(a.activity_at||'')); });
else if(C.sort==='new') rows.sort(function(a,b){ return String(b.created_at||'').localeCompare(String(a.created_at||'')); });
else if(C.sort==='old') rows.sort(function(a,b){ return String(a.created_at||'').localeCompare(String(b.created_at||'')); });
var cc=$('cdirCount');
if(cc) cc.textContent = rows.length ? (rows.length+' كلان'+(q?(' · بحث «'+window._cdir.q+'»'):'')) : '';
if(!rows.length){
box.innerHTML='
'+
(q?'ما لقيت كلاناً بهذا البحث 🔎':'ما فيه كلانات بهذا الفلتر')+'
';
return;
}
box.innerHTML=rows.map(function(c){
var tg=String(c.tag||'؟');
var lg=c.logo_url
? ''
: ''+esc(tg.slice(0,2))+'';
/* ═══ سطر الحالة ═══
«معتمد» كان على كل كلان تقريباً فما عاد يميّز أحداً — شِيل.
والذي يهمّ الزائر: هل بابهم مفتوح للتقديم أم لا. */
var rat=c.status==='ratified';
var stChip=(c.recruiting!==false)
? 'التقديم: مفتوح' : 'التقديم: مقفل';
var door=rat?'':' · ⏳ قيد الترسيم';
return '
'; });
};
window.renderHiring=function(){
var box=$('hiringList'); if(!box) return;
var rows=(window._hiring||[]).slice().sort(function(a,b){
/* المعتمد أولاً، ثم الأنشط */
var ra=(a.status==='ratified')?0:1, rb=(b.status==='ratified')?0:1;
if(ra!==rb) return ra-rb;
return String(b.activity_at||'').localeCompare(String(a.activity_at||''));
});
if(!rows.length){
box.innerHTML='
🚪 ما فيه كلان فاتح باب الانضمام حالياً. انشر طلبك في «أعضاء يبحثون عن كلان» ويشوفك الرؤساء.
';
return;
}
var free=!(window._myClanId||window._myMemberClanId);
box.innerHTML=rows.map(function(c){
var tg=String(c.tag||'؟');
var lg=c.logo_url
? ''
: ''+esc(tg.slice(0,4))+'';
var rat=c.status==='ratified';
var chip=function(txt,bg,bd,cl){ return ''+txt+''; };
var st=rat?chip('✔ معتمد','rgba(61,220,132,.14)','rgba(61,220,132,.4)','#3ddc84')
:chip('⏳ بانتظار الترسيم','rgba(255,204,0,.13)','rgba(255,204,0,.4)','var(--gold)');
var note=c.recruit_note||c.description||'';
return '
':'')+
/* الكلان غير المرسَّم ما يستقبل طلبات — نُعلمه قبل الضغط لا بعده */
(function(){
var waitM=(free&&rat&&window.joinWait)?window.joinWait(c.id):0;
if(waitM>0){
/* أرسل طلباً — لا زرّ، بل حالةٌ تقول متى يعيد */
return '
'+
'📨 أرسلت طلباً أعد بعد '+window.fmtWait(waitM)+'
';
}
var can=free&&rat;
var act = !free ? 'toast(\'🛡️ اخرج من كلانك أولاً\')'
: !rat ? 'toast(\'⏳ ما يستقبل طلبات حتى يُرسَّم من الإدارة\')'
: 'window.joinClanNow(\''+c.id+'\',\''+esc(tg)+'\')';
var lbl = !free ? '🔒 أنت في كلان' : (!rat ? '⏳ بانتظار الترسيم' : '📨 طلب انضمام');
return '';
})()+
'
';
}).join('');
};
/* من هذي القائمة نرجع لها لا للكلانات */
window.openClanFromHiring=function(cid){ window._backTo={screen:'hiring'}; openClan(cid); };
/* ═══════════ أرشيف الكلانات ═══════════
الأرشفة ليست حذفاً: الكلان يخمد ويُحرَّر أعضاؤه، ويبقى سجلّه
وتاقه محجوزين لصاحبه حتى يعود. */
window.loadArchive=function(force){
var box=$('arcList'); if(!box) return;
box.innerHTML='
'; return; }
var rows=r.data||[];
/* كلاناتك أولاً — هي التي تهمّك */
if(window._uid) rows.sort(function(a,b){
var ma=(a.created_by===window._uid)?0:1, mb=(b.created_by===window._uid)?0:1;
return ma-mb;
});
var mineN=window._uid?rows.filter(function(c){return c.created_by===window._uid;}).length:0;
var note=$('arcNote');
if(note && mineN){
note.innerHTML='
'+
'🔓 عندك '+mineN+' كلان في الأرشيف — سجلّه وتاقه محفوظان لك، واسترجاعه بضغطة.'+
' والأرشفة ما تمنعك من الانضمام لكلان آخر.
';
}
else if(note) note.innerHTML=rows.length
? '
🔒 هذي الكلانات خامدة: ما تتحدّى ولا تُتحدّى ولا تستقبل أعضاء. ونتائجها تبقى في السجل العام.
'
: '';
if(!rows.length){
box.innerHTML='
🗂️ الأرشيف فاضي ما اعتزل أي كلان بعد
';
return;
}
box.innerHTML=rows.map(function(c){
var tg=String(c.tag||'؟');
var deld=!!c.deleted_at; /* حذفته الإدارة — لا مجرد أرشفة */
var lg=c.logo_url
? ''
: ''+esc(tg.slice(0,4))+'';
var t=(c.wins||0)+(c.losses||0);
var mine=(window._uid && c.created_by===window._uid) || window._isStaff;
return '
');
return;
}
window._cc.step='تفاصيل التحدّي';
sb.from('challenges').select('*').eq('id',chId).single().then(function(r){
if(window._cc.id !== chId) return; /* سبقتها غرفةٌ أخرى */
var c=r.data; if(!c){ window.putHTML(msgs,'
التحدي غير موجود
'); return; }
window._cc.ch=c;
sb.from('clans').select('id,tag,logo_url').in('id',[c.challenger_clan,c.opponent_clan].filter(Boolean)).then(function(cr){
var m={}; (cr.data||[]).forEach(function(x){ m[x.id]=x; });
/* ═══ التاق يجب أن يُعرف أينما دخلت ═══
خريطة الكلانات كانت تُملأ من شاشة التحديات وحدها، فمن فتح
الغرفة من إشعار أو رابط مباشر رأى «؟» مكان التاق. نملؤها هنا. */
window._clanMap=window._clanMap||{};
for(var _k in m){ if(m.hasOwnProperty(_k)) window._clanMap[_k]=m[_k]; }
var A=m[c.challenger_clan]||{}, B=m[c.opponent_clan]||{};
if($('ccTitle')) $('ccTitle').innerHTML='CLAN '+esc(String(A.tag||'؟'))+' ضد CLAN '+esc(String(B.tag||'؟'));
if($('ccSub')) $('ccSub').textContent='تبادلوا آيديات السوني، أضيفوا بعض، واتفقوا على الوقت';
/* التفاصيل الكاملة داخل الصفحة — كانت متفرّقة بالبطاقة */
/* «كلانك» من القاعدة — _myIds قد تكون فارغة عند الدخول المباشر للرابط */
/* ═══ من أنت في هذه المباراة؟ ═══
لا نُخمّن. إن لم نعرف بعدُ (القاعدة تُجيب بعد لحظة) نعرض
الطرفين بأسمائهما بلا «كلانك/الخصم» — والتخمين الخاطئ أسوأ
من التأخّر لحظة. */
var mineId=window._cc.myClan
|| ((window._myIds&&window._myIds.indexOf(c.challenger_clan)!==-1)?c.challenger_clan:null)
|| ((window._myIds&&window._myIds.indexOf(c.opponent_clan)!==-1)?c.opponent_clan:null)
|| null;
var oppId=mineId?((mineId===c.challenger_clan)?c.opponent_clan:c.challenger_clan):null;
var tm=tDT(c.created_at);
var mm=window._clanMap||{};
var det=$('ccDetails');
/* بيانات القيادة تأتي من شاشة التحديات عادةً — نحمّلها هنا
لمن يفتح صفحة التحدي مباشرةً من إشعار أو رابط */
if(!window._clanContacts || !window._clanContacts[c.challenger_clan] || !window._clanContacts[c.opponent_clan]){
sb.from('clan_members').select('clan_id,role,member_name,psn,profiles(username,psn_id)')
.in('clan_id',[c.challenger_clan,c.opponent_clan].filter(Boolean))
.in('role',['leader','vice']).then(function(mr){
var cc=window._clanContacts||{};
(mr.data||[]).forEach(function(m){
cc[m.clan_id]=cc[m.clan_id]||{};
var ps=(m.psn||(m.profiles&&m.profiles.psn_id)||'').trim();
cc[m.clan_id][m.role]={ name:(ps||m.member_name||(m.profiles&&m.profiles.username)||''), psn:ps };
});
window._clanContacts=cc;
if(window._cc.id===chId && window._cc.repaint) window._cc.repaint();
},function(){});
}
window._cc.repaint=function(){
var mid=window._cc.myClan||mineId;
var oid=(mid===c.challenger_clan)?c.opponent_clan:c.challenger_clan;
mineId=mid; oppId=oid; paintDet();
};
var paintDet=function(){
if(!det) return;
/* ═══ المواجهة ═══
صاحب الطلب يميناً وخصمه يساراً — الجهة تحدّدها المباراة لا
المشاهد، فلا تنقلب على أحد. و«كلانك» شارةٌ تحت جهتك. */
var side=function(id, sd){
var fb=(id===c.challenger_clan)?A:B;
var t=String((mm[id]||fb||{}).tag||'؟');
var lg=(mm[id]||fb||{}).logo_url;
var me=(mineId&&id===mineId);
return '
'+
/* ═══ أنت البطل ═══
كلانك يميناً بالأزرق وخصمك يساراً بالأحمر. ومن يشاهد من
خارج الكلانين (مشرف أو زائر) يرى صاحب الطلب يميناً. */
'
'+ side(mineId||c.challenger_clan,'a')+
'
VS
'+
side(mineId?oppId:c.opponent_clan,'b')+'
'+
'
🎮 أضيفوا بعض على السوني والعبوا — وبعد المباراة وثّقوا النتيجة بالإثبات
'+
/* ═══ قاعدة المهلة تُقال في مكانها ═══
الطرفان يقرآنها هنا قبل أن تقع، لا بعد أن يُلغى التحدي
فيسألا لماذا. والساعات تُقرأ من الإعدادات لا تُكتب رقماً
ثابتاً — فإن غيّرتَها من اللوحة تغيّر النصّ معها. */
((c.status==='accepted'||c.status==='live')
? '
'+
'⏳ مهلة التوثيق '+esc(String((window._appSet||{}).result_confirm_hours||'24'))+' ساعة '+
'وثّق أحدُكما وسكت الآخر ← تُعتمد رواية من وثّق. '+
'ولم يوثّق أحدٌ منكما ← يُلغى التحدي رسمياً وتتحرّر خانتاكما المصنّفة.'+
'
'
: '')+
/* ═══ شرط اعتماد المصنّف ═══
المصنّف يمسّ التصنيف، فدليله أعلى: صور الانتصار في كل قيمٍ
لا لقطةٌ واحدة. يُقال للطرفين هنا قبل اللعب لا بعد الرفض. */
(c.ranked
? '
'+
'🏅 تحدٍّ مصنّف — شرط الاعتماد '+
'لا تُعتمد النتيجة إلا برفع صور الانتصار في القيم كاملةً — وبغير ذلك لا يتم الاعتماد.'+
'
'+
'بقبولك يصير هذا الوقت مُلزِماً للطرفين — من لا يثبت حضوره خلال '+window.apptGrace()+
' دقيقة يُعتبَر منسحباً 4-0، وإن غبتم جميعاً أُلغي التحدي. وإن ما ناسبك الوقت ارفض الطلب.'+
'
'); });
});
};
/* ═══ توقيتُ السعودية ═══
الرياضُ +٣ ثابتاً بلا توقيتٍ صيفيٍّ ولا شتويّ، فالحسابُ اليدويُّ
يقينيّ: لا يتبعُ منطقةَ جهازِ اللاعبِ ولا يقلبُه تقويمٌ هجريٌّ في
`toLocaleString`. **والوقتُ المتَّفَقُ عليه واحدٌ للطرفَين، فلا
يجوزُ أن يراه كلٌّ بساعةِ بلدِه.**
وكلُّ رقمٍ في ``: رقمانِ في سطرٍ عربيٍّ واحدٍ ينقلبانِ بصريّاً. */
var RYD_OFF=3*3600000;
var AR_DAY=['الأحد','الاثنين','الثلاثاء','الأربعاء','الخميس','الجمعة','السبت'];
var AR_MON=['يناير','فبراير','مارس','أبريل','مايو','يونيو','يوليو','أغسطس','سبتمبر','أكتوبر','نوفمبر','ديسمبر'];
function _p2(n){ return (n<10?'0':'')+n; }
function _ryd(iso){ return new Date(new Date(iso).getTime()+RYD_OFF); }
/* ═══ أيُّ يومٍ هو؟ ═══
«٢:٢٢» وحدَها غامضةٌ والمهلةُ يومان: أهي ليلةَ اليومِ أم ليلةَ الغد؟
فتُذكَرُ نسبةُ اليومِ قبلَ اسمِه في **كلِّ** موضعٍ يُعرَضُ فيه الوقت.
والقياسُ بيومِ الرياضِ لا بيومِ الجهاز. */
function _rydKey(x){ var t=_ryd(x); return Date.UTC(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()); }
window.rydRel=function(iso){
var n=Math.round((_rydKey(iso)-_rydKey(Date.now()))/86400000);
return n===0?'اليوم':n===1?'بكرة':n===2?'بعد بكرة':(n===-1?'أمس':'');
};
window.rydDate=function(iso){ var t=_ryd(iso), r=window.rydRel(iso);
return (r?r+' · ':'')+AR_DAY[t.getUTCDay()]+' '+t.getUTCDate()+' '+AR_MON[t.getUTCMonth()]; };
window.rydTime=function(iso){ var t=_ryd(iso), h=t.getUTCHours();
return ''+(h%12||12)+':'+_p2(t.getUTCMinutes())+' '+(h<12?'صباحاً':'مساءً'); };
window.rydPlain=function(iso){ var t=_ryd(iso), h=t.getUTCHours(), r=window.rydRel(iso);
return (r?r+' · ':'')+AR_DAY[t.getUTCDay()]+' '+t.getUTCDate()+' '+AR_MON[t.getUTCMonth()]+
' الساعة '+(h%12||12)+':'+_p2(t.getUTCMinutes())+' '+(h<12?'صباحاً':'مساءً'); };
/* ═══ العددُ العربيُّ يُصرِّفُ معدودَه ═══
«٩ ساعة» خطأٌ يُقرأُ ركيكاً في وجهِ لاعبٍ ينتظرُ موعدَ مباراتِه.
والقاعدة: واحدٌ مفردٌ، واثنانِ مثنّى، ومن ثلاثةٍ إلى عشرةٍ جمعٌ،
ومن أحدَ عشرَ فصاعداً مفردٌ منصوب. */
window.arCount=function(n,one,two,few,many){
if(n===0) return 'أقل من '+one;
if(n===1) return one;
if(n===2) return two;
if(n<=10) return n+' '+few;
return n+' '+many;
};
window.durAr=function(ms){
if(!(ms>0)) ms=0;
var mn=Math.round(ms/60000), h=Math.floor(mn/60), d=Math.floor(h/24);
var D=function(n){ return window.arCount(n,'يوم','يومين','أيام','يوماً'); };
var H=function(n){ return window.arCount(n,'ساعة','ساعتين','ساعات','ساعة'); };
var M=function(n){ return window.arCount(n,'دقيقة','دقيقتين','دقائق','دقيقة'); };
if(d>0) return ''+D(d)+''+((h%24)?' و'+H(h%24)+'':'');
if(h>0) return ''+H(h)+''+((mn%60)?' و'+M(mn%60)+'':'');
return ''+M(mn)+'';
};
window.apptGrace=function(){ return parseInt((window._appSet||{}).appt_grace_min,10)||20; };
/* ═══ إدخالُ موعدِ الاستكمال ═══
الاتّفاقُ يقعُ في اللعبةِ بينَ الرئيسَين — والمنصّةُ **تُوثّقُه
وتُنفّذُه** لا تُفاوضُ عليه. فهذا حقلُ إدخالٍ لما اتُّفِقَ عليه،
لا بابُ مساومةٍ جديد. */
window.askResumeAt=function(maxH){
maxH=maxH||48;
return new Promise(function(done){
['cfBack','cfBox'].forEach(function(i){ var o=document.getElementById(i); if(o) o.remove(); });
var bk=document.createElement('div'); bk.id='cfBack';
var bx=document.createElement('div'); bx.id='cfBox';
var dmin=new Date(Date.now()+RYD_OFF).toISOString().slice(0,10);
var dmax=new Date(Date.now()+RYD_OFF+maxH*3600000).toISOString().slice(0,10);
var i, hop='', mop='', dchip='';
for(i=1;i<=12;i++) hop+='';
for(i=0;i<60;i++) mop+='';
/* ═══ ثلاثةُ أيّامٍ لا أكثر ═══
سقفُ التأجيلِ يومان، فأبعدُ يومٍ ممكنٍ هو «بعد بكرة». وحقلُ
التاريخِ الرقميُّ يُخفي هذه البداهةَ خلفَ تقويمٍ كامل. */
var DCH='flex:1;min-width:0;padding:8px 4px;border-radius:9px;cursor:pointer;'+
'font-family:inherit;text-align:center;line-height:1.5;'+
'background:rgba(0,0,0,.3);border:1px solid rgba(255,255,255,.16);color:var(--tx2)';
var RELN=['اليوم','بكرة','بعد بكرة'];
for(i=0;i<3;i++){
var dd=new Date(Date.now()+RYD_OFF+i*86400000);
dchip+='';
}
var IST='width:100%;padding:9px 10px;border-radius:9px;background:rgba(0,0,0,.3);'+
'border:1px solid rgba(255,255,255,.16);color:var(--tx);font-size:14px;'+
'font-family:inherit;text-align:center';
bx.innerHTML=
'
📅
'+
'
موعد استكمال التحدي
'+
'
اكتب اليوم والساعة التي اتفقت عليها مع رئيس الكلان الآخر داخل اللعبة. '+
'الكلان الآخر يراها في طلب التأجيل، وبقبوله تصير مُلزِمة للطرفين.
'+
'
'+
'
'+
'
اليوم
'+
'
'+dchip+'
'+
''+
'
'+
'
'+
'
الساعة والدقيقة
'+
'
'+
''+
':'+
''+
''+
'
'+
'
'+
''+
'
'+
'
'+
''+
''+
'
';
document.body.appendChild(bk); document.body.appendChild(bx);
var D=bx.querySelector('#raD'), H=bx.querySelector('#raH'),
M=bx.querySelector('#raM'), Pm=bx.querySelector('#raP'),
PV=bx.querySelector('#raPrev'), OK=bx.querySelector('#cfYes');
/* الافتراضُ: بعدَ ساعتَينِ من الآنَ بتوقيتِ الرياض */
var DAYS=[].slice.call(bx.querySelectorAll('.raDay'));
var setDay=function(v){
D.value=v;
DAYS.forEach(function(b){
var on=(b.getAttribute('data-d')===v);
b.style.background = on?'linear-gradient(180deg,rgba(232,179,75,.28),rgba(232,179,75,.14))':'rgba(0,0,0,.3)';
b.style.borderColor = on?'rgba(232,179,75,.75)':'rgba(255,255,255,.16)';
b.style.color = on?'var(--gold)':'var(--tx2)';
});
paint();
};
DAYS.forEach(function(b){ b.onclick=function(){ setDay(b.getAttribute('data-d')); }; });
var d0=new Date(Date.now()+RYD_OFF+2*3600000);
var h0=d0.getUTCHours();
H.value=String(h0%12||12); M.value=String(d0.getUTCMinutes()); Pm.value=(h0<12?'ص':'م');
var calc=function(){
if(!D.value) return null;
var h=(parseInt(H.value,10)%12)+(Pm.value==='م'?12:0);
/* الوقتُ مكتوبٌ بساعةِ الرياض: نبنيه UTC ثمَّ نطرحُ الفارقَ فيصيرُ لحظةً واحدةً للعالم */
var t=Date.parse(D.value+'T'+_p2(h)+':'+_p2(parseInt(M.value,10))+':00Z');
return isNaN(t)?null:(t-RYD_OFF);
};
var paint=function(){
var t=calc(), nw=Date.now();
if(t===null){ PV.innerHTML='حدّد اليوم'; OK.disabled=true; OK.style.opacity=.5; return; }
var dl=t-nw;
if(dl < 15*60000){
PV.innerHTML='⚠️ الموعد قريب جداً — اختر وقتاً بعد ربع ساعة على الأقل';
OK.disabled=true; OK.style.opacity=.5; return;
}
if(dl > maxH*3600000){
PV.innerHTML='⚠️ أقصى تأجيل '+maxH+' ساعة — قرّب الموعد';
OK.disabled=true; OK.style.opacity=.5; return;
}
OK.disabled=false; OK.style.opacity=1;
PV.innerHTML='
📅 '+window.rydDate(t)+'
'+
'
🕐 '+window.rydTime(t)+'
'+
'
بتوقيت السعودية · بعد '+window.durAr(dl)+'
'+
'
من لا يحضر خلال '+
window.apptGrace()+' دقيقة من الموعد يُعتبَر منسحباً
';
};
[H,M,Pm].forEach(function(el){ el.onchange=paint; el.oninput=paint; });
setDay(d0.toISOString().slice(0,10));
var close=function(v){
document.removeEventListener('keydown',key);
bk.remove(); bx.remove(); done(v);
};
var key=function(e){ if(e.key==='Escape') close(null); };
document.addEventListener('keydown',key);
bk.onclick=function(){ close(null); };
bx.querySelector('#cfNo').onclick=function(){ close(null); };
OK.onclick=function(){ if(OK.disabled) return; close(calc()); };
});
};
var POSTPONE_ERR={
NOT_LEADER:'التأجيل لرئيس الكلان أو نائبه فقط',
NOT_FOUND:'التحدي غير موجود',
NOT_ALLOWED:'التأجيل غير متاح لهذا التحدي',
WRONG_STATUS:'حالة التحدي لا تسمح بالتأجيل الآن',
ALREADY_POSTPONED_OTHER:'⏸️ عندكم تحدٍّ آخر مؤجَّل بالفعل — لازم تستأنفوه أو تُنهوه قبل تأجيل تحدٍّ ثانٍ',
MAX_REACHED:'وصلتم الحد الأقصى لطلبات التأجيل لهذا التحدي',
CONCURRENT:'تعذّر الطلب — حاول مرة أخرى',
SAME_CLAN:'أنت صاحب الطلب — الرد يخصّ الكلان الآخر',
RESUME_REQUIRED:'لازم تحدّد موعد استكمال التحدي — حدّث الصفحة إن لم يظهر لك الحقل',
RESUME_TOO_SOON:'الموعد قريب جداً — اختر وقتاً بعد ربع ساعة على الأقل',
RESUME_TOO_FAR:'الموعد أبعد من الحد المسموح للتأجيل',
NO_RESUME_AT:'الطلب أُرسل بلا موعد استكمال — اطلب من الكلان الآخر إعادة الطلب',
RESUME_PASSED:'مضى موعد الاستكمال قبل ردّك — الطلب لم يعد صالحاً'
};
var READY_ERR={
auth:'سجّل دخولك أولاً',
not_found:'التحدي غير موجود',
not_leader:'إثبات الحضور لرئيس الكلان أو نائبه فقط',
no_window:'ما فيه نافذة حضور مفتوحة الآن — انتظر موعد الاستكمال',
expired:'انقضت نافذة الحضور'
};
/* ═══ إثباتُ الحضورِ عندَ الموعد ═══
«كنتُ موجوداً» دعوى، وهذه الضغطةُ أثرٌ مسجَّلٌ بوقتِه — وبها وحدَها
يُفصَلُ بينَ كلانَينِ كلٌّ منهما يقولُ إنّه حضر. */
window.doReadyConfirm=function(chId){
sb.rpc('ready_confirm',{p_ch:chId}).then(function(r){
var d=r&&r.data;
if(r.error||!d||d.ok!==true){
toast('⚠️ '+(READY_ERR[(d&&d.why)||'']||(r.error&&r.error.message)||'تعذّر إثبات الحضور'));
window.openChChat(chId); return;
}
if(d.both) toast('✅ حضر الطرفان — استُؤنف التحدي');
else toast('✋ سُجّل حضورك — بانتظار الكلان الآخر');
window.openChChat(chId);
});
};
window.doRequestPostpone=function(chId){
var _mx=parseInt((window._appSet||{}).postpone_max_hours,10)||48;
window.askResumeAt(_mx).then(function(at){
if(!at) return;
sb.rpc('request_postpone',{p_ch_id:chId,p_resume_at:new Date(at).toISOString()}).then(function(r){
var d=r&&r.data;
if(r.error||(d&&d.error)){
var why=(d&&d.error)||'';
toast('⚠️ '+(POSTPONE_ERR[why]||(r.error&&r.error.message)||'تعذّر الطلب'));
return;
}
toast('⏸️ أُرسل طلب التأجيل ومعه موعد الاستكمال — بانتظار رد الكلان الآخر');
window.openChChat(chId);
});
});
};
window.doRespondPostpone=function(chId,accept){
if(accept){
var _c=(window._cc&&window._cc.ch)||{}, _gr=window.apptGrace();
window.confirmBox({icon:'📅',tone:'gold',title:'قبول التأجيل والموعد؟',
text:(_c.resume_at?'موعد الاستكمال: '+window.rydPlain(_c.resume_at)+' بتوقيت السعودية. ':'')+
'بقبولك يصير الموعد مُلزِماً للطرفين: من لا يثبت حضوره خلال '+_gr+
' دقيقة من الموعد يُعتبَر منسحباً 4-0، وإن غاب الطرفان أُلغي التحدي.',
yes:'نعم، أقبل الموعد',no:'إلغاء'}).then(function(ok){
if(!ok) return;
sb.rpc('respond_postpone',{p_ch_id:chId,p_accept:true}).then(function(r){
var d=r&&r.data;
if(r.error||(d&&d.error)){ toast('⚠️ '+((d&&d.error)||(r.error&&r.error.message)||'تعذّر القبول')); return; }
toast(d&&d.legacy?'⏸️ قُبل التأجيل — التحدي معلَّق الآن'
:'📅 قُبل الموعد — صار مُلزِماً للطرفين');
window.openChChat(chId);
});
});
} else {
sb.rpc('respond_postpone',{p_ch_id:chId,p_accept:false}).then(function(r){
var d=r&&r.data;
if(r.error||(d&&d.error)){ toast('⚠️ '+((d&&d.error)||(r.error&&r.error.message)||'تعذّر الرفض')); return; }
toast('❌ رُفض التأجيل — التحدي مستمر');
window.openChChat(chId);
});
}
};
window.doRequestResume=function(chId){
window.confirmBox({icon:'▶️',tone:'gold',title:'طلب الاستئناف؟',
text:'سيُرسَل طلب للكلان الآخر. التحدي يعود للعب بعد موافقة الطرفين.',
yes:'نعم، أرسل الطلب',no:'إلغاء'}).then(function(ok){
if(!ok) return;
sb.rpc('request_resume',{p_ch_id:chId}).then(function(r){
var d=r&&r.data;
if(r.error||(d&&d.error)){ toast('⚠️ '+((d&&d.error)||(r.error&&r.error.message)||'تعذّر الطلب')); return; }
toast('▶️ أُرسل طلب الاستئناف — بانتظار رد الكلان الآخر');
window.openChChat(chId);
});
});
};
window.doRespondResume=function(chId,accept){
sb.rpc('respond_resume',{p_ch_id:chId,p_accept:accept}).then(function(r){
var d=r&&r.data;
if(r.error||(d&&d.error)){ toast('⚠️ '+((d&&d.error)||(r.error&&r.error.message)||'تعذّر الاستئناف')); return; }
if(accept){ toast('▶️ استُؤنف التحدي — عودوا للعب!'); } else { toast('❌ رُفض الاستئناف — التأجيل لا يزال قائماً'); }
window.openChChat(chId);
});
};
/* من صفحة التحدي إلى نموذج توثيق النتيجة — ونختار التحدي تلقائياً */
/* ═══ قرارات الإشراف على هذا التحدي ═══
الحكم يخصّ المباراة، فيراه الطرفان في صفحتها لا في تنبيه عابر. */
/* ═══════════ بطاقة المشرف ═══════════
الكلانان يريدان معرفة من يحكم بينهما — اسمٌ عابر لا يكفي. */
window.closeMod=function(){
var c=document.getElementById('modCard'), b=document.getElementById('modBack');
/* البطاقة مشتركة بين المشرف واللاعب — فنُنظّف مفوَّض الأفعال معها */
if(c){ c.hidden=true; c.innerHTML=''; c.onclick=null; } if(b) b.hidden=true;
};
window.openMod=function(uid, fallbackName){
var c=document.getElementById('modCard'), b=document.getElementById('modBack');
if(!c||!b) return;
var draw=function(p){
p=p||{};
var nm=p.username||fallbackName||'مشرف';
var av=p.avatar_url
? ''
: '
'+esc(String(nm).slice(0,1).toUpperCase())+'
';
/* الإداريُّ ليس مشرفاً — ولا يُنسَبُ لغيرِ رتبتِه.
و«support» مُهيّأةٌ من الآن: متى مُنِحَ الدورُ ظهرَ اسمُه بلا نشرٍ جديد. */
var R={admin:['🛡️ الإدارة','rgba(255,107,94,.16)','rgba(255,107,94,.45)','#ff8c7a'],
moderator:['🧑⚖️ مشرف','rgba(255,204,0,.14)','rgba(255,204,0,.45)','var(--gold)'],
support:['🛠️ الدعم الفني','rgba(61,220,132,.14)','rgba(61,220,132,.45)','#3ddc84']};
var r=R[p.role]||['🛡️ الإدارة','rgba(56,182,255,.14)','rgba(56,182,255,.45)','var(--blue2)'];
var since=p.created_at?('عضو منذ '+String(p.created_at).slice(0,10)):'';
c.innerHTML=av+
'
'+
(p.id?'':'')+
'';
c.hidden=false; b.hidden=false;
};
if(!uid){ draw(null); return; }
sb.from('profiles').select('id,username,avatar_url,role,title,bio,created_at').eq('id',uid).single()
.then(function(r){ draw(r&&r.data); }, function(){ draw(null); });
};
document.addEventListener('click',function(e){
var t=e.target&&e.target.closest?e.target.closest('.modlink'):null;
if(t) window.openMod(t.getAttribute('data-mod')||'', t.getAttribute('data-modname')||'');
});
document.addEventListener('keydown',function(e){ if(e.key==='Escape') window.closeMod(); });
/* ═══════════════ بطاقة البلاغ ═══════════════
بطاقةٌ واحدة تخدم موضعين: المعلّقة فوق حين تكون قيد الفحص، والنازلة
في المحادثة حين يُرفع البلاغ أو يصدر الحكم — بهيئتها كاملةً:
النوع والتفاصيل والأدلّة، واسم المشرف مع قراره وعلامته. */
window.reportCard=function(p){
if(!p) return '';
var done=(p.status==='resolved');
var held=(!done && !!p.claimed_by_name);
var ok=(p.outcome!=='rejected');
var col = done ? (ok?'#3ddc84':'#ff8c7a') : held ? 'var(--blue2)' : 'var(--gold)';
var bg = done ? (ok?'rgba(61,220,132,.07)':'rgba(255,107,94,.07)')
: held ? 'rgba(56,182,255,.07)' : 'rgba(255,204,0,.07)';
var bd = done ? (ok?'rgba(61,220,132,.4)':'rgba(255,107,94,.4)')
: held ? 'rgba(56,182,255,.4)' : 'rgba(255,204,0,.4)';
var mid=(done?(p.resolved_by||p.claimed_by):p.claimed_by)||'';
var mnm=p.claimed_by_name||'';
var mlink=function(txt){
return ''+esc(txt)+' ⓘ';
};
var ev=String(p.evidence_url||'').split(',').filter(Boolean);
var evh = ev.length ? '
');
}catch(e){ return h; }
};
})();
window.loadVerdicts=function(chId){
var box=$('ccVerdicts'); if(!box) return;
/* ═══ ما الذي يستحقّ التعليق فوق الرأس؟ ═══
البلاغ نفسه ينزل في المحادثة مع بقية الأحداث، والحكم كذلك.
البلاغ يبقى مثبّتاً ما دام معلّقاً — استلمه مشرف أو لم يستلمه —
لأنه شيءٌ ينتظر. وحين يصدر الحكم ينزل في المحادثة ولا يبقى فوق. */
sb.from('reports').select('id,reason,details,status,resolution,created_at,evidence_url,claimed_by_name,claimed_by,resolved_by,outcome,reporter_id,ev_note')
.eq('challenge_id',chId).in('status',['open','reviewing','awaiting_evidence']).order('created_at',{ascending:false}).limit(5).then(function(r){
if(window._cc.id && window._cc.id !== chId) return;
var rows=r.data||[];
if(!rows.length){ box.innerHTML=''; return; }
box.innerHTML=rows.map(function(p){ return window.reportCard(p); }).join('');
},function(){ box.innerHTML=''; });
};
/* البلاغ داخل صفحة التحدي — والتحدي محدَّد سلفاً */
window.goReport=function(chId){
var box=$('ccReport'); if(!box) return;
if(!box.hidden){ box.hidden=true; return; }
box.hidden=false;
var sel=$('rpChallenge');
if(sel){
var has=false;
[].forEach.call(sel.options,function(o){ if(o.value===chId) has=true; });
if(!has){
var c=window._cc&&window._cc.ch, mm=window._clanMap||{};
var a=String((mm[(c||{}).challenger_clan]||{}).tag||'؟');
var b=String((mm[(c||{}).opponent_clan]||{}).tag||'؟');
var o=document.createElement('option'); o.value=chId; o.textContent=a+' ضد '+b;
sel.appendChild(o);
}
sel.value=chId;
/* التحدي محدَّد — نخفي القائمة كي لا يخطئ */
sel.style.display='none';
}
box.scrollIntoView({behavior:'smooth',block:'center'});
};
/* الرجوع الذكي: من صفحة الخصم نعود لصفحة التحدي لا للرئيسية */
window.openClanFrom=function(clanId, chId){
window._backTo={screen:'chchat', ch:chId};
openClan(clanId);
};
window.goBackSmart=function(){
var b=window._backTo;
if(b && b.screen==='chchat' && b.ch){ window._backTo=null; return window.openChChat(b.ch); }
if(b && b.screen==='hiring'){ window._backTo=null; return go('hiring'); }
if(b && b.screen==='archive'){ window._backTo=null; return go('archive'); }
window._backTo=null; window.goBack('clans');
};
/* التوثيق داخل صفحة التحدي — لا ينقلك لصفحة أخرى */
window._vp=[]; window._vpChId=null;
window.goVerify=function(chId){
var box=$('ccVerify'); if(!box) return;
/* نفس التحدي: إغلاق مؤقت — نحتفظ بالصور المرفوعة */
if(!box.hidden && window._vpChId===chId){ box.hidden=true; return; }
/* تحدٍّ جديد = صفر جديد */
if(window._vpChId!==chId) window._vp=[];
window._vpChId=chId;
box.innerHTML='';
var c=window._cc&&window._cc.ch; if(!c) return;
/* ═══ من أنت؟ ═══
الفائز وشطرا النتيجة كانا يُشتقّان من افتراض أنك صاحب الطلب —
فلو وثّق عضوٌ من الطرف الثاني قبل أن تصل هويته، انقلب الفائز
والنتيجة معاً. الآن: لا افتراض، ولكل كلان زرُّه وخانتُه. */
var mine=window._cc.myClan
|| ((window._myIds&&window._myIds.indexOf(c.challenger_clan)!==-1)?c.challenger_clan:null)
|| ((window._myIds&&window._myIds.indexOf(c.opponent_clan)!==-1)?c.opponent_clan:null);
var R=mine||c.challenger_clan; /* يمين */
var L=(R===c.challenger_clan)?c.opponent_clan:c.challenger_clan; /* يسار */
var opp=(R===mine)?L:null;
var mm=window._clanMap||{};
var tg=function(id){ return String((mm[id]||{}).tag||'؟'); };
box.hidden=false;
box.innerHTML=
'
'+
'📸 توثيق نتيجة التحدي'+
'
'+
'اختر ماذا توثّق. الفوز يحتاج إثباتاً، والخسارة تُقبل بلا صور — '+
'وبها تصحّح نتيجة خصمك لو ضخّمها عليك.
'+
'
'+
''+
'
'+
'
'+
''+
'
'+
'
النتيجة — لكل كلان خانته
'+
'
'+
'
CLAN '+esc(tg(R))+'
'+
'
'+
'–'+
'
CLAN '+esc(tg(L))+'
'+
'
'+
'
'+
''+
'
'+
''+
''+
'
'+
''+
''+
'
';
var seg=$('vWin');
if(seg) seg.addEventListener('click',function(e){ var t=e.target.closest('button'); if(!t) return;
[].forEach.call(seg.querySelectorAll('button'),function(x){x.classList.remove('on');}); t.classList.add('on'); });
/* الفوز يحتاج إثباتاً، والخسارة تُقبل بلا صور — فيتبدّل النموذج معهما */
window._vSide='winner';
var sideSeg=$('vSide');
if(sideSeg) sideSeg.addEventListener('click',function(e){
var t=e.target.closest('button'); if(!t) return;
[].forEach.call(sideSeg.querySelectorAll('button'),function(x){x.classList.remove('on');});
t.classList.add('on');
window._vSide=t.getAttribute('data-side');
var pw=$('vProofWrap'), sv=$('vSend'), wm=$('vMsg');
var loser=(window._vSide==='loser');
if(pw) pw.hidden=loser;
if(sv) sv.textContent = loser?'تأكيد الخسارة':'رفع النتيجة للمراجعة';
if(wm){ wm.style.color='var(--tx3)';
wm.textContent = loser
? 'اكتب النتيجة الصحيحة — لو خالفت ما وثّقه خصمك دخل المشرف وحسم بالإثبات.'
: ''; }
});
var fb=$('vFileBtn'), fi=$('vFile'), msg=$('vMsg');
var paint=function(){
var l=$('vList'); if(l) l.innerHTML=window._vp.map(function(u,i){
var isVid=/\.(mp4|mov|webm|avi)(\?|$)/i.test(u);
return ''+
(isVid ? ''
: '')+
'';
}).join('');
if(fb) fb.textContent='📸 أضف صورة إثبات ('+window._vp.length+'/8)';
};
if(fb&&fi){
fb.addEventListener('click',function(){ fi.click(); });
fi.addEventListener('change',function(){
var f=fi.files&&fi.files[0]; if(!f) return;
if(window._vp.length>=8){ msg.style.color='var(--gold)'; msg.textContent='وصلت الحد الأقصى 8 صور'; fi.value=''; return; }
msg.style.color='var(--tx3)'; msg.textContent='⏳ جاري الضغط...';
window.upProof(f, chId, function(pct){
msg.style.color='var(--tx3)'; msg.textContent='⏳ جاري الرفع… '+Math.round(pct*100)+'%';
}).then(function(d){
fi.value='';
if(!d.ok){ msg.style.color='var(--red)'; msg.textContent='تعذّر الرفع: '+d.why; return; }
window._vp.push(d.url); paint();
msg.style.color='#3ddc84'; msg.textContent='✅ أُضيفت ('+window._vp.length+'/8)';
});
});
}
box.addEventListener('click',function(e){
var d=e.target&&e.target.getAttribute&&e.target.getAttribute('data-vdel');
if(d===null||d===undefined) return;
window._vp.splice(parseInt(d,10),1); paint();
});
var snd=$('vSend');
if(snd) snd.addEventListener('click',function(){
var side=window._vSide||'winner';
if(side==='winner' && !window._vp.length){
msg.style.color='var(--red)'; msg.textContent='📎 توثيق الفوز يحتاج صورة إثبات واحدة على الأقل'; return; }
var vr=parseInt((($('vScoreR')||{}).value||'').replace(/[^0-9]/g,''),10);
var vl=parseInt((($('vScoreL')||{}).value||'').replace(/[^0-9]/g,''),10);
if(isNaN(vr)||isNaN(vl)){ msg.style.color='var(--red)'; msg.textContent='اكتب نتيجة كل كلان في خانته'; return; }
/* score_a لصاحب الطلب و score_b لخصمه — تُنسب بالهوية لا بالموضع */
var a=(R===c.challenger_clan)?vr:vl, b=(R===c.challenger_clan)?vl:vr;
var myScore=vr, oppScore=vl; /* R هو كلاني دائماً في هذي الشاشة */
if(side==='winner' && myScore<=oppScore){
msg.style.color='var(--red)'; msg.textContent='⚠️ توثّق فوزاً ونتيجتك أقل — راجع الخانتين'; return; }
if(side==='loser' && myScore>=oppScore){
msg.style.color='var(--red)'; msg.textContent='⚠️ توثّق خسارة ونتيجتك أعلى — راجع الخانتين'; return; }
snd.disabled=true; msg.style.color='var(--gold)'; msg.textContent='...رفع';
sb.rpc('submit_result',{p_ch:chId, p_side:side, p_score_a:a, p_score_b:b,
p_proof:window._vp.join(',')||null}).then(function(r){
snd.disabled=false;
var d=r&&r.data;
if(r&&r.error){ msg.style.color='var(--red)'; msg.textContent=errAr(r.error.message); return; }
if(d&&d.ok===false){
var W={auth:'سجّل دخولك',not_found:'التحدي غير موجود',not_live:'التحدي غير جارٍ',
not_leader:'🔒 التوثيق لرئيس الكلان ونائبه',bad_side:'اختيار غير صالح',
need_proof:'📎 توثيق الفوز يحتاج إثباتاً',already:'وثّق كلانك هذي المباراة من قبل'};
msg.style.color='var(--red)'; msg.textContent=W[d.why]||'تعذّر التوثيق'; return;
}
var S={matched:'✅ تطابقت مع نتيجة خصمك — بانتظار اعتماد المشرف',
disputed:'⚠️ اختلفت عن نتيجة خصمك — المشرف بيحسمها في الغرفة',
await_other:(side==='winner'
? '✅ وُثّقت — تقدر تلعب مصنّفاً جديداً الآن، وننتظر تأكيد خصمك'
: '✅ وُثّقت خسارتك — تقدر تلعب مصنّفاً جديداً الآن')};
msg.style.color=(d&&d.state==='disputed')?'var(--gold)':'#3ddc84';
msg.textContent=S[(d&&d.state)||'await_other'];
window._vp=[]; paint();
setTimeout(function(){ box.hidden=true; box.innerHTML=''; }, 2600);
});
});
box.scrollIntoView({behavior:'smooth',block:'center'});
};
/* عرض الصور داخل التطبيق */
window.isVid=function(u){ return /\.(mp4|mov|webm|avi|m4v)(\?|$)/i.test(String(u||'')); };
/* ═══ قراءة النتيجة ═══
اللاعب يكتبها كما اعتادها: 4-0 · 4 0 · 4VS0 · 4:0 · ٤-٠ — كلها تُقبل.
ما يهمّنا الرقمان لا ما بينهما. */
window.parseScore=function(v){
var t=String(v||'');
/* الأرقام العربية والهندية تُوحَّد أولاً */
t=t.replace(/[٠-٩]/g,function(d){ return String('٠١٢٣٤٥٦٧٨٩'.indexOf(d)); })
.replace(/[۰-۹]/g,function(d){ return String('۰۱۲۳۴۵۶۷۸۹'.indexOf(d)); });
var m=t.match(/(\d{1,3})\s*\D+\s*(\d{1,3})/);
if(!m) return null;
var a=parseInt(m[1],10), b=parseInt(m[2],10);
if(isNaN(a)||isNaN(b)) return null;
return [a,b];
};
/* زر تحميل الدليل — يظهر للمشرف وحده، والخادم يتحقق من الرتبة أيضاً */
window.dlBtn=function(u){
if(!window._isStaff || !/\/media\//.test(String(u||''))) return '';
return '';
};
/* تحميل الدليل لطاقم الإشراف وحده — لعدّ ثواني الزوم بدقّة.
العرض يبقى للطرفين، والتنزيل محروس بالخادم لا بالواجهة فقط. */
/* ═══ حفظ الدليل ═══
على الجوال لا يصل الملف للألبوم إلا عبر ورقة المشاركة (حفظ الفيديو/الصورة).
فنجلب الملف بالتوكن ثم نعرض الورقة؛ وعلى الحاسب نُنزّله مباشرة.
والملف يبقى محفوظاً بالذاكرة كي تكون الضغطة الثانية فورية — لأن
سفاري يشترط أن تخرج الورقة من ضغطة المستخدم لا من انتظار الشبكة. */
window._evBlob=null; window._evKey='';
function evSave(blob, name){
var f=null;
try{ f=new File([blob], name, {type:blob.type||'application/octet-stream'}); }catch(e){}
if(f && navigator.share && navigator.canShare && navigator.canShare({files:[f]})){
return navigator.share({files:[f], title:name}).then(function(){ return 'shared'; },
function(e){ if(e && e.name==='AbortError') return 'cancel'; evDownload(blob,name); return 'dl'; });
}
evDownload(blob, name); return Promise.resolve('dl');
}
function evDownload(blob, name){
var u=URL.createObjectURL(blob), a=document.createElement('a');
a.href=u; a.download=name; a.rel='noopener';
document.body.appendChild(a); a.click();
setTimeout(function(){ a.remove(); URL.revokeObjectURL(u); }, 4000);
}
window.dlEvidence=function(url){
var u=url||window._viewMedia; if(!u) return;
if(!window._isStaff){ toast('تحميل الأدلة لطاقم الإشراف فقط'); return; }
var m=/\/media\/(.+)$/.exec(String(u).split('?')[0]);
if(!m){ toast('تعذّر التحميل'); return; }
var rel=m[1], name='codar-'+rel.split('/').pop();
/* الملف جاهز من ضغطة سابقة — الورقة تفتح فوراً وسفاري يقبلها */
if(window._evKey===rel && window._evBlob) return evSave(window._evBlob, name);
toast('⏳ نجهّز الملف...');
sb.auth.getSession().then(function(sr){
var tok=sr&&sr.data&&sr.data.session&&sr.data.session.access_token;
if(!tok){ toast('سجّل دخولك'); return; }
fetch('/api/dl?f='+encodeURIComponent(rel)+'&t='+encodeURIComponent(tok)).then(function(r){
if(!r.ok) return r.json().then(function(j){ throw new Error(j.error||'تعذّر التحميل'); });
return r.blob();
}).then(function(b){
window._evBlob=b; window._evKey=rel;
return evSave(b, name).then(function(how){
if(how==='dl') toast('✅ نُزّل الملف');
else if(how!=='cancel') toast('✅ اختر «حفظ الفيديو» ليدخل الألبوم');
});
}).catch(function(e){
/* سفاري يرفض الورقة إذا طالت الشبكة — الملف صار بالذاكرة فالضغطة الثانية تنجح */
if(e && e.name==='NotAllowedError') toast('اضغط «حمّل الدليل» مرة ثانية — الملف جاهز');
else toast(e.message||'تعذّر التحميل');
});
});
};
window.openImg=function(u){
var b=$('imgView'), i=$('imgViewSrc'), v=$('vidViewSrc'), a=$('imgViewDl');
if(!b) return;
window._viewMedia=u;
/* الزر للمشرفين فقط */
if(a) a.hidden = !window._isStaff;
if(window.isVid(u)){
/* المقطع بملء الشاشة — الخصم يستحق يشوف الدليل واضحاً */
if(i){ i.hidden=true; i.src=''; }
if(v){ v.hidden=false; v.src=u; try{ v.play(); }catch(e){} }
}else{
if(v){ v.hidden=true; try{ v.pause(); }catch(e){} v.removeAttribute('src'); v.load&&v.load(); }
if(i){ i.hidden=false; i.src=u; }
}
b.hidden=false; document.body.style.overflow='hidden';
};
window.closeImg=function(){
var b=$('imgView'), i=$('imgViewSrc'), v=$('vidViewSrc');
if(b) b.hidden=true;
if(i){ i.src=''; i.hidden=false; }
if(v){ try{ v.pause(); }catch(e){} v.removeAttribute('src'); v.hidden=true; }
document.body.style.overflow='';
};
document.addEventListener('click', function(e){
var t=e.target;
/* closest لأن شارة ▶ تغطّي المصغّرة — الضغطة تصل الغلاف مهما كانت الطبقة */
var box=(t&&t.closest)?t.closest('.ccimg'):null;
if(box){ e.preventDefault(); window.openImg(box.getAttribute('data-full')||box.src); return; }
var dl=(t&&t.closest)?t.closest('[data-dl]'):null;
if(dl){ e.preventDefault(); e.stopPropagation(); window.dlEvidence(dl.getAttribute('data-dl')); }
});
document.addEventListener('keydown', function(e){ if(e.key==='Escape') window.closeImg(); });
/* ═══ ورقة أفعال صفحة التحدي ═══ زر + يجمعها بدل صفٍّ من الأزرار */
window.ccSheetClose=function(){ var e=document.getElementById('ccSheet'); if(e) e.hidden=true; };
(function(){
var plus=document.getElementById('ccPlus'); if(!plus) return;
plus.addEventListener('click', function(){
var sh=document.getElementById('ccSheet'), box=document.getElementById('ccSheetActs');
var src=document.getElementById('ccActs'); if(!sh||!box||!src) return;
var ICON={'وثّق':'📸','بلاغ':'🚩','مشرف':'🛡️','انسحاب':'🏳️','كلان':'🛡️','غرفة':'💬'};
var h='';
[].forEach.call(src.querySelectorAll('button'),function(b,i){
var t=(b.textContent||'').trim();
var ic='⚡'; Object.keys(ICON).forEach(function(k){ if(t.indexOf(k)!==-1) ic=ICON[k]; });
h+='';
});
h+=''+
'';
box.innerHTML=h; sh.hidden=false;
});
document.addEventListener('click', function(e){
var sh=document.getElementById('ccSheet');
if(sh && e.target===sh){ sh.hidden=true; return; }
var r=e.target&&e.target.closest?e.target.closest('[data-ccact],[data-ccx]'):null;
if(!r) return;
var i=r.getAttribute('data-ccact'), x=r.getAttribute('data-ccx');
if(i!==null && i!==undefined){
var b=document.querySelectorAll('#ccActs button')[parseInt(i,10)];
window.ccSheetClose(); if(b) b.click();
} else if(x==='img'){ window.ccSheetClose(); var a=document.getElementById('ccAttach'); if(a) a.click(); }
else if(x==='mic'){ window.ccSheetClose(); var m=document.getElementById('ccMic'); if(m) m.click(); }
});
})();
/* ═══ التحديات الجارية الآن — لوحة عامّة ═══
كل مباراةٍ قائمةٍ بين كلانين تظهر للجميع: CLAN س ضد CLAN ص،
ووسمُ «جاري الآن» ينبض، وعدّادٌ يحسب منذ لحظة القبول (accepted_at)
— يتجدّد كل ثانية بلا نداءٍ للقاعدة، فلا كلفة على الخادم. */
window._liveTick=null;
function liveElapsed(fromISO){
var s=Math.max(0, Math.floor((Date.now()-new Date(fromISO).getTime())/1000));
var h=Math.floor(s/3600), m=Math.floor((s%3600)/60), ss=s%60;
var p=function(n){ return (n<10?'0':'')+n; };
return h>0 ? (h+':'+p(m)+':'+p(ss)) : (p(m)+':'+p(ss));
}
window.renderLiveCards=function(){
var box=$('chLive'); if(!box) return;
var rows=window._liveRows||[];
if(!rows.length){ box.innerHTML='
ما فيه تحديات جارية الآن — تابع «🔥 المتاحة» وكن أول من يطلق ⚡
'; return; }
var mm=window._clanMap||{};
box.innerHTML=rows.map(function(c){
var a=(mm[c.challenger_clan]||{}).tag||'؟', b=(mm[c.opponent_clan]||{}).tag||'؟';
var from=c.accepted_at||c.created_at;
var meta=typeBadge(c.playstyle==='rush'?'rush':'sniper')+' '+rankBadge(c.ranked);
return '
';
}).join('');
};
/* ═══ عرض التحدي الجاري العامّ ═══
الضغط على بطاقةٍ جارية كان يفتح غرفة التحدي للجميع — وهي محميّةٌ
لطرفَيها والطاقم وحدهم. الآن يفتح عرضاً عامّاً: رئيسا الكلانين،
وقت البداية، إحصاءاتهما، وتأثير VS متحرّك — وزرّ دخول الغرفة يظهر
لأهلها فقط (طرفٌ في التحدي أو مشرف). */
window.closeLiveShowcase=function(){ ['lsBack','lsCard'].forEach(function(id){ var e=document.getElementById(id); if(e) e.remove(); }); if(window._lsTick){ clearInterval(window._lsTick); window._lsTick=null; } document.body.style.overflow=''; };
window.openLiveShowcase=function(cid){
if(!cid) return;
var row=(window._liveRows||[]).filter(function(x){return x.id===cid;})[0]; if(!row) return;
window.closeLiveShowcase();
var bk=document.createElement('div'); bk.id='lsBack';
bk.style.cssText='position:fixed;inset:0;background:rgba(3,5,8,.86);backdrop-filter:blur(4px);z-index:240';
bk.onclick=window.closeLiveShowcase;
var cd=document.createElement('div'); cd.id='lsCard';
cd.style.cssText='position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:241;width:min(92vw,420px);'+
'background:linear-gradient(180deg,#0d1016,#070a0e);border:1px solid #1c2230;border-radius:20px;padding:20px 18px;'+
'box-shadow:0 24px 60px rgba(0,0,0,.6)';
cd.innerHTML='
⏳ جاري التحميل...
';
document.body.appendChild(bk); document.body.appendChild(cd);
document.body.style.overflow='hidden';
var from=row.accepted_at||row.created_at;
var ids=[row.challenger_clan,row.opponent_clan];
Promise.all([
sb.from('clans').select('id,tag,points,wins,losses,streak,type').in('id',ids),
sb.from('clan_members').select('clan_id,member_name,psn,role,profiles(username)').in('clan_id',ids).eq('role','leader')
]).then(function(rs){
var cm={}; ((rs[0]&&rs[0].data)||[]).forEach(function(c){ cm[c.id]=c; });
var lead={}; ((rs[1]&&rs[1].data)||[]).forEach(function(m){ lead[m.clan_id]=m.member_name||(m.profiles&&m.profiles.username)||''; });
var A=cm[row.challenger_clan]||{tag:'؟'}, B=cm[row.opponent_clan]||{tag:'؟'};
var side=function(c,ldr,cls){
var wl=(c.wins||0)+'–'+(c.losses||0);
var fire=(c.streak&&c.streak>=2)?' 🔥'+c.streak+'':'';
return '
'+
'
'+esc(String(c.tag||'?').slice(0,4))+'
'+
'CLAN '+esc(c.tag||'?')+''+
'
👑 '+esc(ldr||'—')+'
'+
'
'+
'
🏅 '+(c.points||0)+' نقطة
'+
'
⚔️ '+wl+''+fire+'
'+
'
';
};
var meta=typeBadge(row.playstyle==='rush'?'rush':'sniper')+' '+rankBadge(row.ranked);
/* هل لهذا المستخدم حقّ دخول الغرفة؟ طرفٌ فيها أو مشرف */
var mine=window._myIds||[];
var canEnter=window._isStaff || mine.indexOf(row.challenger_clan)!==-1 || mine.indexOf(row.opponent_clan)!==-1;
cd.innerHTML=
'
'+
'🔴 جاري الآن'+
''+
'
'+
'
'+
side(A,lead[row.challenger_clan],'a')+
'
VS
'+
side(B,lead[row.opponent_clan],'b')+
'
'+
'
'+meta+'
'+
'
⏱️ منذ بداية التحدي: '+
''+liveElapsed(from)+'
'+
(canEnter
? ''
: '
🔒 غرفة التحدي خاصّة بالكلانين والطاقم — وأنت تشاهد من الخارج 👀
')+
'';
/* عدّاد حيّ داخل العرض */
window._lsTick=setInterval(function(){
var el=cd.querySelector('.liveclock'); if(el) el.textContent=liveElapsed(el.getAttribute('data-from'));
},1000);
},function(){ cd.innerHTML='
تعذّر التحميل
'; });
};
function liveTickAll(){
if(document.hidden) return;
var sec=document.getElementById('secLive');
if(sec && sec.classList.contains('fhide')) return; /* القسم مخفيّ — لا تشغيل */
var scr=document.getElementById('challenges');
if(!scr || !scr.classList.contains('active')) return; /* لسنا في التحديات */
var els=document.querySelectorAll('#chLive .liveclock');
if(!els.length) return;
[].forEach.call(els,function(el){ el.textContent=liveElapsed(el.getAttribute('data-from')); });
}
window.loadLiveChallenges=function(){
var box=$('chLive'); if(!box) return;
if(!window._liveRows) box.innerHTML='
⏳ جاري التحميل...
';
/* نحتاج خريطة الكلانات للأسماء — إن غابت نجلبها */
var ensureClans = (window._clanMap && Object.keys(window._clanMap).length)
? Promise.resolve()
: sb.from('clans').select('id,tag,type').then(function(r){ var m={}; (r.data||[]).forEach(function(c){m[c.id]=c;}); window._clanMap=Object.assign(window._clanMap||{},m); });
ensureClans.then(function(){
sb.from('challenges').select('id,challenger_clan,opponent_clan,playstyle,ranked,status,created_at,accepted_at')
.in('status',['accepted','live']).order('accepted_at',{ascending:false,nullsFirst:false})
.then(function(r){
window._liveRows=(r.data||[]).filter(function(c){ return c.challenger_clan && c.opponent_clan; });
var n=window._liveRows.length;
var b=$('chLiveN'); if(b) b.textContent = n?('· '+n):'';
var chip=document.querySelector('#chChips button[data-f="live"]');
if(chip){ chip.style.borderColor = n?'rgba(255,60,60,.55)':''; chip.style.color = n?'var(--red)':''; }
window.renderLiveCards();
/* عدّادٌ واحد يحرّك كل البطاقات — يعمل ما دام القسم ظاهراً */
if(!window._liveTick) window._liveTick=setInterval(liveTickAll,1000);
},function(){ if(!window._liveRows) box.innerHTML='
تعذّر التحميل
'; });
});
};
/* ═══ حبوب التحديات ═══ تُظهر قسماً وتُخفي البقية */
window.chFilterTo=function(f){
/* «يحتاج ردّك» يجمع ما تحدّاك مباشرةً وما تطوّع لطلبك — كلاهما ينتظر قرارك */
var M={ live:['secLive','chLive'],
open:['secOpen','chOpen'],
need:['secDirected','chDirected','bidsSec','chBids'],
mine:['secMine','chFilter','chMine'] };
if(f==='live' && window.loadLiveChallenges) window.loadLiveChallenges();
Object.keys(M).forEach(function(k){
M[k].forEach(function(id){
var e=document.getElementById(id); if(!e) return;
e.classList.toggle('fhide', k!==f);
});
});
/* والحبّة تُضيء مع قائمتها — وإلا بدا الترشيح معطّلاً */
try{
[].forEach.call(document.querySelectorAll('#chChips button'),function(b){
b.classList.toggle('on', b.getAttribute('data-f')===f);
});
}catch(e){}
};
/* ═══ عدّاد تحدياتي القائمة ═══
التحدي النشط شأن كل أعضاء الكلان — فيظهر رقمه على الحبّة
ليعرف الجميع أن لهم مباراة قائمة بلا أن يفتحوا القائمة. */
window.chCountMine=function(){
var ids=(window._myIds&&window._myIds.length)?window._myIds
:(window._myClanId?[window._myClanId]:[]);
var chip=document.querySelector('#chChips button[data-f="mine"]');
var b=document.getElementById('chMineN');
if(!ids.length){ if(b) b.textContent=''; if(chip){ chip.style.borderColor=''; chip.style.color=''; } return 0; }
var f='('+ids.map(function(i){ return 'challenger_clan.eq.'+i+',opponent_clan.eq.'+i; }).join(',')+')';
sb.from('challenges').select('id',{count:'exact',head:true})
.or(f.slice(1,-1))
.in('status',['open','pending','accepted','live'])
.then(function(r){
var n=(r&&r.count)||0;
if(b) b.textContent = n ? ('· '+n) : '';
if(chip){
chip.style.borderColor = n ? 'rgba(232,179,75,.55)' : '';
chip.style.color = n ? 'var(--gold)' : '';
}
},function(){});
};
/* عدّاد ما ينتظر ردّك — شارةٌ حمراء تلفت لِما يحتاج قرارك */
function setChBadge(spanId, n){
var b=document.getElementById(spanId); if(!b) return;
if(n>0){ b.textContent=n>99?'99+':String(n); b.hidden=false; }
else { b.textContent=''; b.hidden=true; }
}
window.chCountNeed=function(){
var n=0;
['chDirected','chBids'].forEach(function(id){
var box=document.getElementById(id); if(!box) return;
[].forEach.call(box.children,function(c){
var t=(c.getAttribute('style')||'');
if(t.indexOf('text-align:center')===-1) n++; /* رسالة الفراغ لا تُعدّ */
});
});
setChBadge('chNeedN', n);
return n;
};
/* ═══ عدّاد الجارية ═══ رقمٌ أحمر على «الجارية» بعدد المباريات القائمة الآن */
window.chCountLive=function(){
sb.from('challenges').select('id',{count:'exact',head:true})
.in('status',['accepted','live'])
.not('challenger_clan','is',null).not('opponent_clan','is',null)
.then(function(r){ setChBadge('chLiveN',(r&&r.count)||0); },function(){});
};
/* ═══ عدّاد المتاحة ═══ كإشعار الدردشة: يعدّ الطلبات الجديدة التي لم ترها بعد.
«رأيتَها» = آخر مرّة فتحتَ فيها قسم «المتاحة». نحفظ الوقت محلياً. */
function openSeenTS(){ return parseInt(localStorage.getItem('chOpenSeen')||'0',10)||0; }
window.chCountOpen=function(){
var seen=openSeenTS();
sb.from('challenges').select('created_at,challenger_clan')
.is('opponent_clan',null).eq('status','open')
.order('created_at',{ascending:false}).limit(40)
.then(function(r){
var mine=window._myIds||[];
var rows=(r.data||[]).filter(function(c){ return mine.indexOf(c.challenger_clan)===-1; });
var fresh=rows.filter(function(c){ return new Date(c.created_at).getTime() > seen; }).length;
setChBadge('chOpenN', fresh);
},function(){});
};
/* فتحُ قسمٍ يمسح شارته — كفتح محادثة يمسح غير المقروء */
window.chMarkSeen=function(f){
if(f==='open'){ try{ localStorage.setItem('chOpenSeen', String(Date.now())); }catch(e){} setChBadge('chOpenN',0); }
if(f==='need'){ setChBadge('chNeedN',0); }
if(f==='live'){ setChBadge('chLiveN',0); }
};
document.addEventListener('click', function(e){
var b=e.target&&e.target.closest?e.target.closest('#chChips button'):null;
if(!b) return;
[].forEach.call(b.parentNode.children,function(x){ x.classList.remove('on'); });
b.classList.add('on');
var f=b.getAttribute('data-f');
window.chFilterTo(f);
if(window.chMarkSeen) window.chMarkSeen(f);
});
/* ═══ حبوب تصفّح الكلانات ═══ تقود القوائم المخفية */
document.addEventListener('click', function(e){
var b=e.target&&e.target.closest?e.target.closest('#cdirChips button'):null;
if(!b) return;
var k=b.getAttribute('data-k'), v=b.getAttribute('data-v');
/* ═══ حبّة واحدة تحمل اتجاهين ═══
ضغطةٌ ثانية على «الأحدث» تقلب القائمة إلى «الأقدم» والعكس —
فيرفع العضو القائمة وينزّلها بزرٍّ واحد بدل حبّتين. */
if(b.id==='cdirAge' && b.classList.contains('on')){
v = (v==='new') ? 'old' : 'new';
b.setAttribute('data-v', v);
b.textContent = (v==='new') ? '🆕 الأحدث' : '🕰️ الأقدم';
}
var peers=[].filter.call(b.parentNode.children,function(x){ return x.getAttribute('data-k')===k; });
peers.forEach(function(x){ x.classList.remove('on'); });
b.classList.add('on');
if(k==='filter'){ var f=document.getElementById('cdirFilter'); if(f){ f.value=v; window._cdir.filter=v; } }
else { var so=document.getElementById('cdirSort'); if(so){ so.value=v; window._cdir.sort=v; if(v==='random') window._cdir.seed=Math.random(); } }
if(window.renderClanDir) window.renderClanDir();
});
/* ═══ أزرار الخصوصية المقسّمة ═══ تُحفظ فور الضغط */
document.addEventListener('click', function(e){
var b=e.target&&e.target.closest?e.target.closest('.pillx button'):null;
if(!b) return;
var host=b.parentNode, sel=document.getElementById(host.getAttribute('data-for'));
if(!sel) return;
[].forEach.call(host.children,function(x){ x.classList.toggle('on', x===b); });
sel.value=b.getAttribute('data-v');
var sv=document.getElementById('stSavePriv'); if(sv) sv.click();
var m=document.getElementById('stPrivMsg');
if(m){ m.style.color='#3ddc84'; m.textContent='✅ انحفظ'; setTimeout(function(){ m.style.color=''; m.textContent='يُحفظ فور اختيارك — بلا زر حفظ.'; }, 1800); }
});
/* مزامنة الحبوب مع القيم المحفوظة */
window.syncPills=function(){
[].forEach.call(document.querySelectorAll('.pillx'),function(h){
var sel=document.getElementById(h.getAttribute('data-for')); if(!sel) return;
[].forEach.call(h.children,function(x){ x.classList.toggle('on', x.getAttribute('data-v')===sel.value); });
});
};
/* ═══ الصفوف القابلة للطيّ ═══ صفٌّ واحد مفتوح في المرة */
document.addEventListener('click', function(e){
var r=e.target&&e.target.closest?e.target.closest('.lrow[data-open]'):null;
if(!r) return;
var id=r.getAttribute('data-open'), body=document.getElementById(id);
if(!body) return;
var opening=body.hidden;
var host=r.closest('.rows');
if(host) [].forEach.call(host.querySelectorAll('.lbody'),function(b){
b.hidden=true; var t=host.querySelector('.lrow[data-open="'+b.id+'"]'); if(t) t.classList.remove('open');
});
body.hidden=!opening; r.classList.toggle('open', opening);
if(opening) setTimeout(function(){ body.scrollIntoView({behavior:'smooth',block:'nearest'}); }, 60);
});
/* زر الخروج الجديد يستدعي القديم */
(function(){ var lr=document.getElementById('acLogoutRow');
if(lr) lr.addEventListener('click',function(){ var b=document.getElementById('btnLogout'); if(b) b.click(); }); })();
/* ═══ مرفقات غرفة التحدي ═══ */
function ccMsg(t, col){ var e=$('ccUpMsg'); if(e){ e.style.color=col||'var(--gold)'; e.textContent=t||''; } }
/* الصور تُضغط قبل الرفع — التسجيلات تُرفع كما هي */
function ccShrink(file){
return new Promise(function(res){
if(!/^image\//.test(file.type)) return res(file);
var img=new Image(), u=URL.createObjectURL(file);
img.onload=function(){
var mx=1280, w=img.width, h=img.height;
if(w>mx||h>mx){ if(w>h){ h=Math.round(h*mx/w); w=mx; } else { w=Math.round(w*mx/h); h=mx; } }
var cv=document.createElement('canvas'); cv.width=w; cv.height=h;
cv.getContext('2d').drawImage(img,0,0,w,h);
cv.toBlob(function(b){ URL.revokeObjectURL(u); res(b||file); },'image/jpeg',0.82);
};
img.onerror=function(){ URL.revokeObjectURL(u); res(file); };
img.src=u;
});
}
window.ccUpload=function(blob, kind, dur){
var chId=window._cc.id; if(!chId||!blob) return;
ccMsg('⏳ جاري الرفع...');
sb.auth.getSession().then(function(sr){
var tok=sr&&sr.data&&sr.data.session&&sr.data.session.access_token;
if(!tok){ ccMsg('سجّل دخولك','var(--red)'); return; }
/* نُخبرُ الخادمَ بما اختارَه المستخدم: حاويةُ mp4 واحدةٌ للمقطعِ
وللتسجيلِ الصوتيّ، والبصمةُ وحدَها لا تفرّق. */
return window.upXHR({'Authorization':'Bearer '+tok,'x-codar-kind':'chat','x-codar-ch':chId,
'x-codar-media':(kind==='audio'?'audio':kind==='video'?'video':'image')},
blob, function(p){ ccMsg('⏳ جاري الرفع… '+Math.round(p*100)+'%'); })
.then(function(x){
if(!x.ok||!x.j||!x.j.url){ ccMsg((x.j&&x.j.error)||'تعذّر الرفع','var(--red)'); return; }
var row={challenge_id:chId, user_id:window._uid, clan_id:window._cc.myClan||null,
username:(window._myUsername||'لاعب'),
body:(kind==='audio'?'🎙️ تسجيل صوتي':kind==='video'?'🎬 مقطع':'📎 صورة'),
kind:kind, media_url:x.j.url};
if(dur) row.dur=dur;
return sb.from('challenge_chat').insert(row).then(function(ir){
if(ir&&ir.error){ ccMsg('تعذّر الإرسال: '+ir.error.message,'var(--red)'); return; }
ccMsg(''); window.loadChChat(true);
});
});
}).catch(function(){ ccMsg('تعذّر الاتصال','var(--red)'); });
};
(function(){
var afi=$('ccAudioFile');
if(afi) afi.addEventListener('change',function(){
var f=afi.files&&afi.files[0]; if(!f) return;
if(f.size>4*1024*1024){ ccMsg('⚠️ التسجيل كبير (الحد 4MB)','var(--red)'); afi.value=''; return; }
window.ccUpload(f,'audio'); afi.value='';
});
var fb=$('ccAttach'), fi=$('ccFile');
if(fb&&fi){
fb.addEventListener('click',function(){ fi.click(); });
fi.addEventListener('change',function(){
var f=fi.files&&fi.files[0]; if(!f) return;
/* ═══ المقطعُ دليلٌ كالصورة ═══
كانت الغرفةُ تقبلُ الصورَ والتسجيلاتِ وحدَها، والخادمُ يقبلُ المقاطعَ
منذُ البداية — فكان النزاعُ على «إطالةِ الزوم» لا يُحسَمُ في غرفتِه
لأن الدليلَ نفسَه لا يدخل. صار يدخل، وسقفُه سقفُ الخادمِ نفسُه
(٣٠MB) لا سقفاً أضيقَ يردُّ الناسَ بلا سبب. */
var vid=/^video\//.test(f.type||'')||/\.(mp4|mov|webm|m4v)$/i.test(f.name||'');
if(vid){
if(f.size>30*1024*1024){ ccMsg('⚠️ المقطع أكبر من 30MB — قصّه أو أرسله كدليل في البلاغ','var(--red)'); fi.value=''; return; }
window.ccUpload(f,'video'); fi.value=''; return;
}
if(f.size>8*1024*1024){ ccMsg('⚠️ الصورة كبيرة جداً','var(--red)'); fi.value=''; return; }
ccShrink(f).then(function(b){ window.ccUpload(b,'image'); fi.value=''; });
});
}
/* التسجيل الصوتي — MediaRecorder مدعوم بسفاري iOS 14.3+ */
var rec=null, chunks=[], t0=0, tick=null;
function stopTick(){ if(tick){ clearInterval(tick); tick=null; } }
function showRec(on){
var b=$('ccRec'); if(b) b.hidden=!on;
var m=$('ccMic'); if(m) m.style.opacity = on?'.4':'1';
}
function endRec(send){
stopTick();
if(!rec) return;
rec._send=send;
try{ if(rec.state!=='inactive') rec.stop(); }catch(e){}
}
/* بديل مضمون: مسجّل النظام — يعمل حتى لو مُنع المايك داخل التطبيق */
function micFallback(why){
var fi2=$('ccAudioFile');
if(!fi2){ ccMsg(why||'التسجيل غير متاح','var(--red)'); return; }
ccMsg((why?why+' — ':'')+'اختر تسجيلاً من جهازك','var(--gold)');
fi2.click();
}
var mic=$('ccMic');
if(mic) mic.addEventListener('click',function(){
if(rec){ endRec(true); return; }
if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia || !window.MediaRecorder){
return micFallback('جهازك ما يدعم التسجيل المباشر');
}
navigator.mediaDevices.getUserMedia({audio:true}).then(function(stream){
var opts={};
['audio/mp4','audio/webm;codecs=opus','audio/webm','audio/ogg'].some(function(t){
if(window.MediaRecorder.isTypeSupported&&MediaRecorder.isTypeSupported(t)){ opts.mimeType=t; return true; }
return false;
});
chunks=[]; rec=new MediaRecorder(stream,opts); rec._send=false;
rec.ondataavailable=function(e){ if(e.data&&e.data.size) chunks.push(e.data); };
rec.onstop=function(){
stream.getTracks().forEach(function(t){ t.stop(); });
showRec(false);
var send=rec._send, dur=Math.round((Date.now()-t0)/1000);
rec=null;
if(!send){ chunks=[]; ccMsg(''); return; }
if(dur<1){ chunks=[]; ccMsg('التسجيل قصير جداً','var(--red)'); return; }
var blob=new Blob(chunks,{type:(opts.mimeType||'audio/webm')}); chunks=[];
window.ccUpload(blob,'audio',dur);
};
t0=Date.now(); rec.start(); showRec(true); ccMsg('');
tick=setInterval(function(){
var d=Math.floor((Date.now()-t0)/1000);
var e=$('ccRecT'); if(e) e.textContent=Math.floor(d/60)+':'+('0'+(d%60)).slice(-2);
if(d>=120) endRec(true); // سقف دقيقتان
},250);
},function(err){
var n=(err&&err.name)||'';
/* نسمّي السبب بدل رسالة عامة — يفرق كثيراً بالدعم */
if(n==='NotAllowedError' || n==='SecurityError')
ccMsg('🎙️ رفضت إذن المايك — فعّله من إعدادات الجهاز ثم أعد المحاولة','var(--red)');
else if(n==='NotFoundError') micFallback('ما لقيت مايك');
else micFallback('تعذّر التسجيل المباشر ('+(n||'خطأ')+')');
});
});
var st=$('ccRecStop'); if(st) st.addEventListener('click',function(){ endRec(true); });
var cn=$('ccRecCancel'); if(cn) cn.addEventListener('click',function(){ endRec(false); });
})();
/* ═══ استدعاء مشرف للغرفة ═══ */
window.askChatMod=function(chId){
var why=prompt('وش المشكلة؟ (اختياري — تساعد المشرف يفهم بسرعة)');
if(why===null) return;
sb.rpc('request_chat_mod',{p_ch:chId, p_reason:why||null}).then(function(r){
var d=r&&r.data;
if(r&&r.error){ toast(errAr(r.error.message)); return; }
if(d&&d.ok){ toast('🛡️ وصل طلبك لطاقم الإشراف'); window.loadChChat(true); if(window.renderModReq) window.renderModReq(chId); return; }
var w=(d&&d.why)||'';
toast(w==='already' ? '🛡️ فيه طلب مشرف قائم — انتظروا دخوله'
: w==='forbidden' ? 'هذي الغرفة لأعضاء الكلانين'
: w==='not_active' ? 'التحدي مو قائماً' : 'تعذّر الطلب');
},function(){ toast('تعذّر الاتصال'); });
};
/* شارة الطلب القائم + زر دخول المشرف */
window.renderModReq=function(chId){
var box=$('ccModBar'); if(!box) return;
sb.from('chat_mod_requests').select('id,reason,status,created_at')
.eq('challenge_id',chId).eq('status','open').limit(1).then(function(r){
var v=(r.data||[])[0];
if(!v){ box.hidden=true; box.innerHTML=''; return; }
box.hidden=false;
box.innerHTML='
'+
'🛡️'+
'
طلب مشرف قائم'+
(v.reason?' '+esc(v.reason)+'':'')+'
'+
(window._isStaff?'':'')+
'
';
},function(){ box.hidden=true; });
};
window.joinAsMod=function(chId){
sb.rpc('close_chat_mod',{p_ch:chId}).then(function(r){
if(r&&r.error){ toast(errAr(r.error.message)); return; }
toast('🛡️ دخلت الغرفة كمشرف'); window.loadChChat(true); window.renderModReq(chId);
},function(){ toast('تعذّر الاتصال'); });
};
/* ═══ عمر الغرفة عمر المباراة ═══
تُفتح بالقبول وتُقفل بانتهائها. والقاعدة تمنع الكتابة، فيجب أن
تختفي خانة الكتابة أيضاً — الخانة التي لا تعمل أسوأ من غيابها. */
window.ccRoomState=function(st){
var live = (st==='accepted' || st==='live');
var paused = (st==='postponed' || st==='postpone_requested' || st==='resume_requested');
var bar = document.getElementById('ccBar') ||
(document.getElementById('ccInput') ? document.getElementById('ccInput').parentNode : null);
var note = document.getElementById('ccClosed');
if(!note && bar && bar.parentNode){
note=document.createElement('div');
note.id='ccClosed';
note.style.cssText='margin:0 14px 12px;background:rgba(255,255,255,.03);border:1px dashed rgba(255,255,255,.14);'+
'border-radius:12px;padding:12px 13px;font-size:11.5px;line-height:1.9;color:var(--tx3);text-align:center';
bar.parentNode.insertBefore(note, bar);
}
if(bar) bar.style.display = (live || paused) ? '' : 'none';
if(note){
note.hidden = live;
note.innerHTML = paused
? '⏸️ التحدي مؤجَّل — الغرفة لا تزال مفتوحة يمكنكم التنسيق هنا · اضغط "▶️ طلب الاستئناف" للعودة'
: (!live ? '🔒 أُقفلت هذه الغرفة انتهت المباراة، والمحادثة محفوظة للإشراف. أي تحدٍّ جديد يفتح غرفةً جديدة.' : '');
}
};
/* ═══ اللقبُ يُرسَمُ مرّةً واحدة ═══
`ccRole` تُقدّمُ اللقبَ الشخصيَّ على كلِّ صفةٍ لأنَّ من مُنِحَ لقباً بعينِه
قُصِدَ به أن يُرى. **و`fxBadge` تُعيدُ اللقبَ نفسَه بعدَ الاسمِ من المصدرِ
نفسِه** — فظهرَ «PRO MODERATOR» مرّتَين في سطرٍ واحد.
فمن رُسِمَ لقبُه قبلَ اسمِه لا يُعادُ بعدَه، **ومن لا لقبَ له يبقى ما بعدَ
اسمِه كما كان** — فلا تُفقَدُ شارةٌ من أجلِ إصلاحِ تكرار. */
window._ccBadgeShown = function(uid, bdg){
try{ if(window._ccBadge && window._ccBadge[uid] && window._ccBadge[uid].badge_name) return ''; }catch(e){}
return bdg || '';
};
window._incomingCallTimeout = null;
window._incomingCallChallengeId = null;
function playIncomingCallSound(){
try {
var ctx = new (window.AudioContext||window.webkitAudioContext)();
[0, 0.35].forEach(function(delay){
var osc = ctx.createOscillator();
var gain = ctx.createGain();
osc.connect(gain); gain.connect(ctx.destination);
osc.frequency.value = 880;
gain.gain.setValueAtTime(0.0001, ctx.currentTime + delay);
gain.gain.exponentialRampToValueAtTime(0.15, ctx.currentTime + delay + 0.02);
gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + delay + 0.25);
osc.start(ctx.currentTime + delay);
osc.stop(ctx.currentTime + delay + 0.3);
});
} catch(e){}
try { if(navigator.vibrate) navigator.vibrate([200,100,200]); } catch(e){}
}
window.showIncomingCall = function(challengeId, body, avatarUrl){
var modal = $('incomingCallOverlay'); if(!modal) return;
if(window._voice && window._voice.ws) return;
window._incomingCallChallengeId = challengeId;
var bodyEl = $('incomingCallBody'); if(bodyEl) bodyEl.textContent = body;
var av = $('incomingCallAvatar');
if(av){
if(avatarUrl){
av.innerHTML = '';
} else {
var callerName = (body||'').split(' ')[0] || '؟';
av.innerHTML = esc(callerName.slice(0,2).toUpperCase());
}
}
modal.hidden = false;
playIncomingCallSound();
var cd = $('incomingCallCountdown');
if(cd){ cd.style.animation='none'; void cd.offsetWidth; cd.style.animation='incomingCountdown 30s linear forwards'; }
var secLeft = 30;
var cdText = $('incomingCallCountdownText');
if(cdText) cdText.textContent = '0:'+(secLeft<10?'0':'')+secLeft;
if(window._incomingCallTickInt) clearInterval(window._incomingCallTickInt);
window._incomingCallTickInt = setInterval(function(){
secLeft--;
if(cdText) cdText.textContent = '0:'+(secLeft<10?'0':'')+Math.max(secLeft,0);
if(secLeft <= 0) clearInterval(window._incomingCallTickInt);
}, 1000);
if(window._incomingCallTimeout) clearTimeout(window._incomingCallTimeout);
window._incomingCallTimeout = setTimeout(function(){ window.declineIncomingCall(); }, 30000);
};
window.declineIncomingCall = function(){
var modal = $('incomingCallOverlay'); if(modal) modal.hidden = true;
if(window._incomingCallTimeout) clearTimeout(window._incomingCallTimeout);
window._incomingCallChallengeId = null;
};
window.acceptIncomingCall = function(){
var challengeId = window._incomingCallChallengeId;
var modal = $('incomingCallOverlay'); if(modal) modal.hidden = true;
if(window._incomingCallTimeout) clearTimeout(window._incomingCallTimeout);
if(!challengeId) return;
if(window.openChChat) window.openChChat(challengeId);
setTimeout(function(){
if(window.startVoiceCall) window.startVoiceCall(true);
}, 600);
};
/* ═══ المكالمة الصوتية لقناة التحدي — نسخة Mesh (تدعم 3 أطراف) ═══ */
window._voice = { peers:{}, ws:null, localStream:null, muted:false, startedAt:null, timerInt:null, speakInt:null, audioCtx:null, localAnalyser:null, myAvatar:'', myName:'' };
window.checkVoiceAccess = async function(){
var btn = $('ccVoiceBtn');
if(!btn || !window._cc || !window._cc.id || !window._uid) { if(btn) btn.hidden = true; return; }
try {
var roomR = await sb.from('challenges').select('voice_room_active').eq('id',window._cc.id).single();
var roomActive = !!(roomR && roomR.data && roomR.data.voice_room_active);
window._voiceRoomActive = roomActive;
var sess = await sb.auth.getSession();
var token = sess && sess.data && sess.data.session && sess.data.session.access_token;
if(!token) { btn.hidden = true; return; }
if(roomActive){
var rJoin = await fetch('https://cod-ar.com/voice-api/turn-credentials', {
method:'POST',
headers:{ 'Content-Type':'application/json', 'Authorization':'Bearer '+token },
body: JSON.stringify({ challengeId: window._cc.id, intent:'check' })
});
var jJoin = await rJoin.json();
btn.hidden = !(jJoin && jJoin.ok);
if(!btn.hidden){
btn.textContent = '🎙️ انضم للغرفة الصوتية'; btn.dataset.mode='join';
try {
var rMembers = await fetch('https://cod-ar.com/voice-api/room-members', {
method:'POST',
headers:{ 'Content-Type':'application/json', 'Authorization':'Bearer '+token },
body: JSON.stringify({ challengeId: window._cc.id })
});
var jMembers = await rMembers.json();
var prevEl = $('ccVoiceMembersPreview');
if(prevEl && jMembers && jMembers.ok && jMembers.members && jMembers.members.length){
var names = jMembers.members.map(function(m){
var roleLabel = m.isStaff ? 'إداري' : (m.role==='leader'?'قائد':(m.role==='vice'?'نائب':(m.role==='coordinator'?'منسّق':'')));
return m.name + (roleLabel ? ' ('+roleLabel+')' : '');
});
prevEl.textContent = '🎙️ بالغرفة الآن: ' + names.join('، ');
prevEl.hidden = false;
} else if(prevEl) { prevEl.hidden = true; }
} catch(e){}
}
} else {
var rStart = await fetch('https://cod-ar.com/voice-api/turn-credentials', {
method:'POST',
headers:{ 'Content-Type':'application/json', 'Authorization':'Bearer '+token },
body: JSON.stringify({ challengeId: window._cc.id, intent:'check-start' })
});
var jStart = await rStart.json();
btn.hidden = !(jStart && jStart.ok);
if(!btn.hidden){ btn.textContent = '🎙️ فتح غرفة صوتية'; btn.dataset.mode='start'; }
}
} catch(e){ btn.hidden = true; }
};
function voiceFormatTime(sec){
var m = Math.floor(sec/60), s = sec%60;
return (m<10?'0':'')+m+':'+(s<10?'0':'')+s;
}
function voiceStartTimer(){
if(window._voice.startedAt) return;
window._voice.startedAt = Date.now();
var el = $('voiceBarTimer');
window._voice.timerInt = setInterval(function(){
if(!el) return;
var sec = Math.floor((Date.now() - window._voice.startedAt)/1000);
el.textContent = voiceFormatTime(sec);
}, 1000);
}
function voiceSetupSpeakDetection(stream){
try {
if(!window._voice.audioCtx) window._voice.audioCtx = new (window.AudioContext||window.webkitAudioContext)();
var ctx = window._voice.audioCtx;
var src = ctx.createMediaStreamSource(stream);
var analyser = ctx.createAnalyser();
analyser.fftSize = 256;
src.connect(analyser);
var data = new Uint8Array(analyser.frequencyBinCount);
return { read:function(){
analyser.getByteFrequencyData(data);
var sum = 0; for(var i=0;i 14;
var speakingNames = [];
if(youSpeak) speakingNames.push('أنت');
Object.keys(window._voice.peers).forEach(function(pid){
var p = window._voice.peers[pid];
if(p.analyser && p.analyser.read() > 14) speakingNames.push(p.name || 'مشارك');
var dot = document.getElementById('voiceDot_'+pid);
if(dot) dot.style.opacity = (p.analyser && p.analyser.read() > 14) ? '1' : '0.25';
});
var youDot = $('voiceYouDot'); if(youDot) youDot.style.opacity = youSpeak ? '1' : '0.25';
if(speakLabel) speakLabel.textContent = speakingNames.length ? (speakingNames.join('، ') + ' يتحدث') : 'قناة تحدٍّ صوتية';
}, 200);
}
async function voiceCheckQuality(pc, peerId){
try {
var stats = await pc.getStats();
var quality = 'good';
stats.forEach(function(report){
if(report.type === 'candidate-pair' && report.state === 'succeeded'){
var rtt = report.currentRoundTripTime;
if(rtt != null){
quality = rtt > 0.3 ? 'bad' : (rtt > 0.12 ? 'ok' : 'good');
}
}
if(report.type === 'inbound-rtp' && report.kind === 'audio'){
if(report.packetsLost && report.packetsReceived && report.packetsReceived > 0){
var lossRate = report.packetsLost / (report.packetsLost + report.packetsReceived);
if(lossRate > 0.08) quality = 'bad';
else if(lossRate > 0.03 && quality === 'good') quality = 'ok';
}
}
});
if(window._voice.peers[peerId]) window._voice.peers[peerId].quality = quality;
var dot = document.getElementById('voiceQuality_'+peerId);
if(dot){
var qColor = quality === 'good' ? '#4cc978' : quality === 'ok' ? '#e8b34b' : '#ff6b5e';
var qTitle = quality === 'good' ? 'اتصال ممتاز' : quality === 'ok' ? 'اتصال متوسط' : 'اتصال ضعيف';
dot.style.background = qColor;
dot.title = qTitle;
}
} catch(e){}
}
function voiceCreatePeerConnection(peerId, name, avatar, creds, isStaff, role, clanTag, platformRole, uid){
var pc = new RTCPeerConnection({ iceServers:[
{ urls:'stun:stun.l.google.com:19302' },
{ urls:creds.turnUrl, username:creds.username, credential:creds.password }
]});
window._voice.localStream.getTracks().forEach(function(t){ pc.addTrack(t, window._voice.localStream); });
pc.ontrack = function(e){
var audioEl = document.createElement('audio');
audioEl.autoplay = true;
audioEl.id = 'voiceAudio_'+peerId;
audioEl.srcObject = e.streams[0];
document.body.appendChild(audioEl);
window._voice.peers[peerId].analyser = voiceSetupSpeakDetection(e.streams[0]);
voiceRenderPeerAvatars();
voiceStartTimer();
voiceStartSpeakLoop();
voiceUpdateStatusText();
var ring = $('voiceConnectingRing'); if(ring) ring.style.display = 'none';
var qInt = setInterval(function(){
if(!window._voice.peers[peerId]){ clearInterval(qInt); return; }
voiceCheckQuality(pc, peerId);
}, 3000);
window._voice.peers[peerId].qualityInt = qInt;
};
pc.onicecandidate = function(e){
if(e.candidate && window._voice.ws){
window._voice.ws.send(JSON.stringify({ type:'candidate', to:peerId, candidate:e.candidate }));
}
};
window._voice.peers[peerId] = { pc:pc, name:name, avatar:avatar, analyser:null, isStaff:!!isStaff, role:role||null, clanTag:clanTag||null, platformRole:platformRole||null, uid:uid||null };
return pc;
}
function voiceRenderPeerAvatars(){
var list = $('voicePeerAvatars'); if(!list) return;
var html = '';
Object.keys(window._voice.peers).forEach(function(pid){
var p = window._voice.peers[pid];
var inner = p.avatar ? '' : (p.name||'؟').slice(0,2);
html += '
';
};
var _h4490=rows.map(function(m){
/* ═══ رسالةٌ معطوبةٌ لا تُسقِطُ الغرفة ═══
سطرٌ واحدٌ يرمي كان يُفقِدُ الرسائلَ كلَّها. **والقارئُ يريدُ
تسعاً وتسعينَ رسالةً سليمةً أكثرَ ممّا يريدُ كمالاً لا يناله.**
فيُعزَلُ كلُّ صفٍّ في محاولتِه، ويُعلَنُ المعطوبُ مكانَه وحدَه. */
try{ return _ccRow(m); }catch(_re){
try{ console.error('[chchat-row]', _re, m && m.id); }catch(_x){}
return '
'+
'تعذّر عرض رسالة
';
}
});
function _ccRow(m){
/* رسالة تحمل هوية بلاغ ⟵ تُرسم بطاقتَه كاملة */
if(m.system && m.ref_id && window._cc.reps && window._cc.reps[m.ref_id]){
return window.reportCard(window._cc.reps[m.ref_id]);
}
if(m.system){
/* حمولةٌ بين قوسَين خاصَّين ⟦…⟧ تعني قرارَ طاقمٍ يُرسَمُ وثيقةً.
ومن فشلَ تحليلُها رجعَ إلى الرسمِ العاديِّ — **لا تسقطُ رسالةٌ
من أجلِ شكل.** */
var _vm = String(m.body||'').match(/⟦([\s\S]+)⟧/);
if(_vm){
try{
var _vd = JSON.parse(_vm[1]);
return ccStampDoc(_vd, String(m.body||'').split('\n')[0], m.created_at);
}catch(_ve){}
}
/* ═══ لون الرسالة يقول حكمها ═══
البلاغ ذهبيّ (شكوى)، والحكم أخضر إن قُبل وأحمر إن رُفض —
يُقرأ من لونه قبل أن يُقرأ نصّه. */
var K={
closed: ['rgba(255,255,255,.03)','rgba(255,255,255,.12)','var(--tx3)'],
report: ['rgba(255,204,0,.07)','rgba(255,204,0,.4)','var(--gold)'],
verdict_ok:['rgba(61,220,132,.08)','rgba(61,220,132,.45)','#3ddc84'],
verdict_no:['rgba(255,107,94,.08)','rgba(255,107,94,.45)','#ff8c7a'],
/* النقضُ والاسترجاعُ كهرمانيّانِ: ليسا قبولاً ولا رفضاً */
verdict_revert:['rgba(232,153,58,.09)','rgba(232,153,58,.5)','#f0a94a'],
restored:['rgba(232,153,58,.07)','rgba(232,153,58,.32)','#e8993a']
};
var k=K[m.kind]||['rgba(56,182,255,.07)','rgba(56,182,255,.25)','var(--blue2)'];
var bd0=String(m.body||''), nl=bd0.indexOf('\n');
var hd=(nl>0)?bd0.slice(0,nl):bd0, rest=(nl>0)?bd0.slice(nl+1):'';
return '
'+
'
'+esc(hd)+'
'+
(rest?'
'+esc(rest)+'
':'')+
'
'+tHM(m.created_at)+'
';
}
var mine=m.user_id===window._uid;
/* مرفق: صورة تُعرض، وتسجيل يُشغَّل بمشغّل المتصفح */
var att='';
if(m.media_url){
var mu=String(m.media_url).replace(/"/g,'"');
att = (m.kind==='audio')
? ''
: window.isVid(mu)
/* المقاطع تُعرض كمصغّرة تُفتح بملء الشاشة — لا كصورة مكسورة */
? ''+
''+
'▶'
: '';
}
/* ═══ الغرفة تعرف من دخلها ═══
كانت رسائل الغرفة كلها بقالبٍ واحد: لا إطار مجموعة ولا شارة.
فالمشرف الداخل من اللوحة يظهر كأنه لاعبٌ ثالث لا يُعرف — وكلمته
في نزاعٍ على نتيجة يجب أن تُقرأ من لونها قبل نصّها. فصارت الغرفة
تقرأ المظهر نفسه الذي تقرأه الدردشة العامة والخاصة. */
var f=window.fxOf(m.user_id), fb=window.fxBubble(f);
var nm=f?window.fxName(f,m.username||'لاعب'):esc(m.username||'لاعب');
var bdg=window.fxBadge?window.fxBadge(f,9):'';
/* ═══ الوجهُ قبلَ الاسم ═══
غرفةُ التحدّي تجمعُ كلانَينِ لا يعرفُ أحدُهما الآخر، والاسمُ وحدَه
سطرٌ يُقرَأُ ولا يُلمَح. **والصورةُ تُميّزُ المتكلّمَ قبلَ أن يُقرَأَ
اسمُه** — فيصيرُ الردُّ في نزاعٍ محتدمٍ موجَّهاً لشخصٍ لا لسطر.
وهي من النداءِ الجماعيِّ نفسِه الذي يجلبُ الصفةَ والشارة.
ومن لا صورةَ له فحرفانِ من اسمِه، ومن لا هُويّةَ له (رسالةُ نظام)
فراغٌ بعرضِها ليبقى الاصطفافُ مستقيماً. */
var _au = window._ccAva && window._ccAva[m.user_id];
var _ini = esc(String(m.username || '?').slice(0, 2).toUpperCase());
var _ava = m.user_id
? ''
+ (_au ? ''
: _ini)
+ ''
: '';
/* ═══ مفتاحُ الكتم ═══
المشرفُ داخلَ الغرفةِ يرى ما لا تراهُ اللوحة، **وقرارُ الإسكاتِ
يجبُ أن يكونَ حيثُ وقعَ السبب** لا بعدَ خروجٍ ودخول. ولا يظهرُ
إلّا للطاقم، ولا على رسالةِ نفسِه ولا على رسالةِ النظام. */
var _mut = (window._isStaff && m.user_id && m.user_id !== window._uid)
? '🔇'
: '';
return '
'+
''+
'');
/* 7) قواعد ثابتة */
h+=riCard('📌 قواعد ثابتة',
'
'+
'• نقاطك ما تنزل تحت الصفر مهما خسرت. '+
'• التحديات غير المصنّفة ما تمسّ نقاطك ولا ستريكك إطلاقاً. '+
'• النتيجة ما تُحتسب إلا بعد توثيق المشرف بالإثبات. '+
'• الانسحاب من تحدٍّ قائم = خسارة 4-0. '+
'• سجلّك المصنّف وغير المصنّف منفصلان — لكل واحد لوحته.
');
box.innerHTML=h;
var go2=function(){
var a=parseInt($('riA').value,10)||0, b=parseInt($('riB').value,10)||0;
var s=($('riS').value||'4-2').split(/[-–]/);
var w=Math.abs(parseInt(s[0],10)||4), l=Math.abs(parseInt(s[1],10)||0);
var out=$('riOut'); out.innerHTML='
'+
(ranked
? 'في المصنّف تحدٍّ واحد — تنفكّ خانتك لحظة توثيق النتيجة لا بعد اعتماد المشرف.'
: 'في غير المصنّف حتى '+cap+' تحديات معاً، بشرط أن يكون كلٌّ منها مع كلانٍ مختلف.')+
'
';
},function(){ box.hidden=true; });
};
/* ═══ التقدّم للطلبات المفتوحة ═══
صاحب الطلب هو من يختار خصمه — ما يصير أي كلان يخطف تحديه. */
window.bidChallenge=function(chId){
if(!window._uid){ go('account'); return; }
sb.rpc('bid_challenge',{p_ch:chId}).then(function(r){
var d=r&&r.data;
if(r&&r.error){ toast(errAr(r.error.message)); return; }
if(d&&d.ok){ toast('🙋 تقدّمت للتحدي — ننتظر موافقة CLAN صاحب الطلب'); window.loadChallengesScreen(); return; }
var w=(d&&d.why)||'';
toast(w==='closed' ? '⏳ الطلب انسحب أو ارتبط بخصم'
: w==='own' ? 'هذا طلب كلانك أنت'
: w==='not_leader' ? '👑 التقدّم لرئيس الكلان ونائبه'
: w==='already' ? '🙋 أنت متقدّم لهذا الطلب — ننتظر ردّه'
: w==='need_members'? ('⚠️ كلانك يحتاج '+(d.min||6)+' أعضاء على الأقل')
: w==='no_slot' ? ('⚠️ خاناتك ممتلئة — كلانك يخوض '+(d.slots||1)+' تحدياً بالحد الأقصى')
: 'تعذّر التقدّم');
},function(){ toast('تعذّر الاتصال'); });
};
/* ═══════════════════════════════════════════════════════════
ورقةُ تثبيتِ المباراة
───────────────────────────────────────────────────────────
كان القبولُ يقعُ بضغطةٍ واحدة، والرفضُ وحدَه يسأل — وهو مقلوب:
القبولُ هو الذي لا رجعةَ فيه. وقائمةُ التقدّماتِ يُعادُ رسمُها
كلَّما وصلَ تقدّمٌ جديد، فتنزلقُ البطاقاتُ تحتَ الإصبعِ وتقعُ
الضغطةُ على غيرِ من رأى. فصارت المباراةُ لا تبدأُ إلا بعدَ أن
يُقرأَ اسمُ الخصمِ ويُضغَطَ عليه بعينِه — والقاعدةُ تُقابِلُ ما
رآه بما هو مسجَّلٌ عندها، فإن اختلفا لم تقعْ مباراة.
═══════════════════════════════════════════════════════════ */
window.matchConfirm=function(o){
var old=document.getElementById('mcfWrap'); if(old) old.remove();
var w=document.createElement('div'); w.id='mcfWrap';
w.style.cssText='position:fixed;inset:0;z-index:9999;background:rgba(2,5,9,.82);'+
'display:flex;align-items:flex-end;justify-content:center;backdrop-filter:blur(3px)';
w.innerHTML='
'+
'
تثبيتُ المباراة
'+
'
'+
'CLAN '+esc(o.tag)+'
'+
'
'+esc(o.line||'')+'
'+
'
'+
'⚠️ بعد التثبيت تُحجَز خانةُ كلانك وتُلغى طلباتكم الأخرى. تأكّدْ أن هذا هو الخصم المقصود.
'+
''+
'
';
document.body.appendChild(w);
var done=false;
var close=function(){ if(!done){ done=true; w.remove(); if(o.onClose) o.onClose(); } };
w.querySelector('#mcfNo').addEventListener('click',close);
w.addEventListener('click',function(e){ if(e.target===w) close(); });
w.querySelector('#mcfGo').addEventListener('click',function(){
var b=this; if(b.disabled) return; b.disabled=true; b.textContent='… لحظة';
done=true; w.remove(); o.onYes();
});
};
window.answerBid=function(bidId, ok, expectClan, tag){
if(!ok && !confirm('ترفض تقدّم هذا الكلان؟')) return;
sb.rpc('answer_bid',{p_bid:bidId, p_accept:!!ok, p_expect_clan:expectClan||null}).then(function(r){
var d=r&&r.data;
if(r&&r.error){ toast(errAr(r.error.message)); return; }
if(d&&d.ok){
if(d.act==='accepted'){
toast('⚡ بدأ التحدي ضد CLAN '+String(d.clan||'').toUpperCase());
if(d.challenge && window.openChChat) setTimeout(function(){ window.openChChat(d.challenge); }, 700);
} else toast('رفضت التقدّم');
window.loadChallengesScreen(); return;
}
var w=(d&&d.why)||'';
/* ═══ اختلفَ ما رآه عمّا في القاعدة ═══
تحرّكتِ القائمةُ تحتَ إصبعه فوقعتِ الضغطةُ على بطاقةٍ أخرى.
القاعدةُ ردّتها ولم تقعْ مباراة — وهذا هو المقصودُ بالضبط. */
if(w==='mismatch'){
toast('⚠️ تحرّكت القائمة — كنت تقصد CLAN '+String((d&&d.saw)||'؟').toUpperCase()+
' والزرّ صار لـCLAN '+String((d&&d.actual)||'؟').toUpperCase()+'. ما بدأ شيء، أعد المحاولة.');
window.loadChallengesScreen(); return;
}
/* الرسالةُ تقولُ لماذا ومتى يزول المانع — لا «اختر غيره» وكفى */
var _cap=(d&&d.cap), _used=(d&&d.used);
toast(w==='forbidden' ? '👑 صاحب الطلب وحده يقرّر'
: w==='closed' ? '⏳ الطلب ما عاد مفتوحاً'
: w==='opp_few_members' ? ('⚠️ هذا الكلان أعضاؤه أقل من '+((d&&d.need)||6))
: w==='opp_no_slot'? ((d&&d.ranked)!==false
? ('🏅 هذا الكلان عنده '+(_used||'')+' من '+(_cap||'')+' تحديات مصنّفة قائمة — يفضى بعد توثيق نتائجها')
: ('🤝 هذا الكلان عنده '+(_used||'')+' من '+(_cap||'')+' تحديات غير مصنّفة قائمة'))
: w==='done' ? 'تمّ الردّ على هذا التقدّم' : 'تعذّرت العملية');
},function(){ toast('تعذّر الاتصال'); });
};
window.pullBid=function(bidId){
sb.rpc('pull_bid',{p_bid:bidId}).then(function(){ toast('سحبت تقدّمك'); window.loadChallengesScreen(); },
function(){ toast('تعذّر الاتصال'); });
};
/* المتقدّمون لطلباتي + تقدّماتي المعلّقة */
window.loadBids=function(myIds){
var box=$('chBids'), sec=$('bidsSec'); if(!box) return;
if(!myIds||!myIds.length){ box.innerHTML=''; if(sec) sec.hidden=true; return; }
sb.from('challenge_bids').select('*,challenges(id,challenger_clan,opponent_clan,status,playstyle,ranked)')
.eq('status','pending').order('created_at',{ascending:false}).limit(60).then(function(r){
var rows=(r.data||[]).filter(function(b){ return b.challenges && b.challenges.status==='open'; });
var mm=window._clanMap||{};
var toMe=rows.filter(function(b){ return myIds.indexOf(b.challenges.challenger_clan)!==-1; });
var mine=rows.filter(function(b){ return myIds.indexOf(b.clan_id)!==-1; });
if(!toMe.length && !mine.length){ box.innerHTML=''; if(sec) sec.hidden=true; return; }
if(sec) sec.hidden=false;
var h='';
h+=toMe.map(function(b){
var tg=String((mm[b.clan_id]||{}).tag||'؟');
return '
'+
'
🙋 CLAN '+esc(tg)+' يبي يتحدّاك
'+
'
'+
(b.note?esc(b.note)+' ':'')+'وافق ليبدأ التحدي وتنفتح غرفة التواصل بينكم.
'+
'
'+
''+
''+
''+
'
';
}).join('');
h+=mine.map(function(b){
var og=String((mm[b.challenges.challenger_clan]||{}).tag||'؟');
return '
'+
'
⏳ تقدّمت لطلب CLAN '+esc(og)+'
'+
'
ننتظر موافقته — تقدر تسحب تقدّمك.
'+
'
'+
'
';
}).join('');
box.innerHTML=h;
},function(){ box.innerHTML=''; if(sec) sec.hidden=true; });
};
function updateCh(id,patch,msg){ sb.from('challenges').update(patch).eq('id',id).then(function(r){ if(r.error){toast(errAr(r.error.message));return;} toast(msg); window.loadChallengesScreen(); }); }
function acceptCh(id){
window.authUser().then(function(res){ var user=res&&res.data&&res.data.user; if(!user){go('account');return;}
sb.rpc('my_challenge_clan',{p_user:user.id}).then(function(cr){
var myClan=(cr&&cr.data)||null;
sb.from('challenges').select('opponent_clan,challenger_clan,ranked').eq('id',id).single().then(function(ch){
var openCh = ch.data && !ch.data.opponent_clan;
var acceptClan = openCh ? myClan : (ch.data&&ch.data.opponent_clan);
if(!acceptClan){ toast('سجّل كلانك أولاً لتقبل'); return; }
sb.from('clan_members').select('id',{count:'exact',head:true}).eq('clan_id',acceptClan).then(function(mc){
var _mn=window.chMinMembers();
if((mc.count||0)<_mn){ toast('⚠️ كلانك يحتاج '+_mn+' أعضاء على الأقل للتحدي'); return; }
var doAccept=function(){
sb.rpc('core_accept_challenge',{p_cid:id, p_opp_clan:acceptClan, p_actor:(window._myUsername||'لاعب'), p_discord:null,
p_expect_challenger:(ch.data&&ch.data.challenger_clan)||null}).then(function(r){
if(r&&r.error){ toast(errAr(r.error.message)); return; }
if(r&&r.data==='mismatch'){ toast('⚠️ تحرّكت القائمة — ما بدأ شيء، أعد المحاولة'); window.loadChallengesScreen(); return; }
if(r&&r.data==='unavailable'){ toast('⏳ انتهت مدة الطلب أو قُبل من غيرك'); window.loadChallengesScreen(); return; }
/* نسخةٌ قديمةٌ في كاشِ أحدهم قد تُرسل قبولاً لطلبٍ مفتوح — القاعدةُ ترُدّه، ونحوّله تقدّماً */
if(r&&r.data==='needs_bid'){ window.bidChallenge(id); return; }
toast('✅ قبلت التحدي — أُلغيت طلباتكم الأخرى'); window.loadChallengesScreen();
});
};
/* ═══ الحصّة تُسأل من القاعدة لا تُحسب هنا ═══
كان هنا عدٌّ مستقلٌّ لأي تحدٍّ «نشط» — لا يعرف أن توثيق
النتيجة يفكّ الحجز، ولا يفرّق بين المصنّف وغيره. فيبقى
من وثّق خسارته ممنوعاً بلا سبب. المرجع واحد: القاعدة. */
if(openCh){
var isRk = (ch.data && ch.data.ranked!==false);
sb.rpc('clan_mode_slots',{p_clan:acceptClan, p_ranked:isRk}).then(function(sr){
var s=sr&&sr.data;
/* ═══ «ok» يجمع سببين فلا يُقال بلسانٍ واحد ═══
ok=false تعني «نقصُ أعضاء» أو «امتلاءُ خانات» — وسؤالٌ
واحدٌ عنهما يُخرج جواباً واحداً، فيُقال لمن ينقصه عضو
إنّ عنده تحدياً قائماً. نفصل السببين ونسمّي كلاً باسمه. */
if(s && (s.members||0) < (s.min||6)){
toast('⚠️ كلانك يحتاج '+(s.min||6)+' أعضاء على الأقل للتحدي'); return; }
if(s && (s.used||0) >= (s.cap||1)){
toast(isRk
? '🏅 خاناتك المصنّفة ممتلئة ('+(s.used||0)+' من '+(s.cap||1)+') — وثّق نتيجةً أولاً'
: '🤝 وصلت حدّ التحديات غير المصنّفة ('+(s.cap||4)+')');
return;
}
doAccept();
},function(){ doAccept(); }); /* تعذّر السؤال؟ القاعدة تحرس أصلاً */
} else doAccept();
});
});
});
});
}
var chScreen=$('challenges');
if(chScreen) chScreen.addEventListener('click',function(e){
var t=e.target; if(!t.getAttribute) return;
var a=t.getAttribute('data-acc'), rj=t.getAttribute('data-rej'), wd=t.getAttribute('data-wd'), dm=t.getAttribute('data-dismiss');
var bid=t.getAttribute&&t.getAttribute('data-bid');
if(bid){ window.bidChallenge(bid); return; }
var bok=t.getAttribute&&t.getAttribute('data-bidok');
var bno=t.getAttribute&&t.getAttribute('data-bidno');
var bpl=t.getAttribute&&t.getAttribute('data-bidpull');
if(bno){ window.answerBid(bno, false); return; }
if(bok){
var bc=t.getAttribute('data-bidclan'), bt=t.getAttribute('data-bidtag')||'؟';
window.matchConfirm({ tag:bt, line:'تقدّم لطلبكم المفتوح — بعد التثبيت تنفتح غرفة التحدي بينكم.',
onYes:function(){ window.answerBid(bok, true, bc, bt); } });
return;
}
if(bpl){ window.pullBid(bpl); return; }
var nsw=t.getAttribute&&t.getAttribute('data-noshow');
if(nsw){ window.reportNoShow(nsw); return; }
if(a) acceptCh(a);
else if(rj) updateCh(rj,{status:'cancelled'},'رفضت التحدي');
else if(wd){ window.quitChallenge(wd); }
else if(dm){ var card=t.closest&&t.closest('.ch'); if(card) card.style.display='none'; }
});
[['chBo'],['chPs'],['chRanked'],['rsWinner'],['drWinner'],['chPostpone']].forEach(function(x){ var g=$(x[0]); if(g) g.querySelectorAll('button').forEach(function(b){ b.addEventListener('click',function(){ g.querySelectorAll('button').forEach(function(y){y.classList.remove('on')}); b.classList.add('on');
if(x[0]==='chRanked' && window.renderSlots) window.renderSlots(window._myClanId);
if(x[0]==='chPostpone'){ var pn=$('chPostponeNote'); if(pn) pn.style.display=(b.getAttribute('data-v')==='1')?'block':'none'; }
}); }); });
/* شرح يتبدّل مع نوع التحدي */
(function(){
var g=$('chRanked'), n=$('chRankNote'); if(!g||!n) return;
g.addEventListener('click',function(e){
var b=e.target.closest('button'); if(!b) return;
n.innerHTML = b.getAttribute('data-v')==='1'
? '🏅 المصنّف — النقاط تزيد بالفوز وتنقص بالخسارة. الفوز 4-0 أكثر من 4-3، والفوز على كلان أقوى منك يعطيك أضعاف.'
: '🤝 غير المصنّفة — بلا نقاط ولا مخاطرة. النتيجة تنحسب بسجلك غير المصنّف وتظهر بصفحة كلانك، وتصنيفك ما يتأثر إطلاقاً.';
n.style.background = b.getAttribute('data-v')==='1' ? 'rgba(255,204,0,.05)' : 'rgba(61,220,132,.05)';
n.style.borderColor = b.getAttribute('data-v')==='1' ? 'rgba(255,204,0,.22)' : 'rgba(61,220,132,.25)';
});
})();
/* ═══ شاشة رفع النتيجة القديمة أُزيلت ═══
كانت تُدرج في challenge_results مباشرةً: بلا جهة ولا كلانٍ موثِّق
ولا مهلة — فصفُّها لا يُحرّر خانةً ولا ينقضي. التوثيق كلّه صار
يمرّ عبر submit_result من غرفة التحدي، وهي وحدها من يعرف
المصنّف من غيره، ويضبط المهلة، ويكتب في الغرفة، ويُشعر الطرفين. */
if($('btnReqCh')) $('btnReqCh').addEventListener('click',function(){
window.authUser().then(function(res){ var user=res&&res.data&&res.data.user;
if(!user){ $('chMsg').textContent='سجّل دخولك أولاً'; go('account'); return; }
var myClan=$('chMyClan').value; if(!myClan){ $('chMsg').textContent='اختر كلانك (أو سجّل كلان أولاً)'; return; }
var oppTag=($('chOpp').value||'').trim().toUpperCase();
var opp=oppTag?((window._clanByTag&&window._clanByTag[oppTag])||null):null;
if(oppTag && !opp){ $('chMsg').textContent='ما لقيت كلان بهذا التاق — تأكّد أو اتركه فاضي'; return; }
var boB=$('chBo').querySelector('button.on'); var bo=boB?parseInt(boB.getAttribute('data-v'),10):4;
var psB=$('chPs').querySelector('button.on'); var ps=psB?psB.getAttribute('data-v'):'sniper';
if((window._appSet||{}).hide_rush==='1') ps='sniper'; /* الرشاش مخفيّ ⇒ لا يُنشأ تحدٍّ به */
var status=opp?'pending':'open';
$('chMsg').textContent='...فحص';
/* الخانات: كل فريق كامل = تحدٍّ متزامن. الفحص من القاعدة أيضاً. */
/* ═══ الحصّة تُسأل بحسب النوع ═══
الفحص القديم كان يعدّ خانةً واحدة مشتركة بمنطق عدد الأعضاء —
لا يعرف أن توثيق النتيجة يفكّ الحجز، ولا يفرّق مصنّفاً عن غيره.
فيمنع من وثّق خسارته بحجّة «عندك تحدٍّ قائم». */
(function(){
var rkBtn=$('chRanked')?$('chRanked').querySelector('button.on'):null;
var ranked=!rkBtn||rkBtn.getAttribute('data-v')==='1';
sb.rpc('clan_mode_slots',{p_clan:myClan, p_ranked:ranked}).then(function(sr){
var sd=sr&&sr.data||{};
if((sd.members||0)<(sd.min||6)){
$('chMsg').textContent='⚠️ كلانك يحتاج '+(sd.min||6)+' أعضاء على الأقل للتحدي'; return; }
if((sd.used||0)>=(sd.cap||1)){
$('chMsg').textContent = ranked
? ('🏅 خاناتك المصنّفة ممتلئة ('+(sd.used||0)+' من '+(sd.cap||1)+') — وثّق نتيجةً أولاً ثم اطلب غيره')
: ('🤝 وصلت حدّ التحديات غير المصنّفة ('+(sd.cap||4)+') — خلّص واحداً منها');
return; }
var _chPostBtn=$('chPostpone')&&$('chPostpone').querySelector('button.on');
var _allowPost=!!(_chPostBtn&&_chPostBtn.getAttribute('data-v')==='1');
var _pMaps=_allowPost?parseInt(($('chPostponeMaps')||{}).value||'0',10)||null:null;
sb.from('challenges').insert({challenger_clan:myClan,opponent_clan:opp,best_of:bo,playstyle:ps,type:'team',status:status,ranked:ranked,created_by:user.id,allow_postpone:_allowPost,postpone_after_maps:_pMaps}).select().then(function(r){
if(r.error){
var em=String(r.error.message||'');
/* ═══ الخصمُ أولاً ثم أنت ═══
«RANKED_BUSY» جزءٌ من «OPP_RANKED_BUSY»، فلو سُئل عنه أوّلاً
ابتلع خبر الخصم ونسبه إليك. الترتيب هنا ليس ذوقاً: الأخصُّ
يُسأل قبل الأعمّ، وإلا اتُّهم البريء بتحدٍّ لا يملكه. */
$('chMsg').textContent =
/OPP_POSTPONED_RANKED/.test(em) ? '⏸️ خصمك عنده تحدٍّ مصنّف مؤجَّل — انتظر انتهاء تأجيله'
: /POSTPONED_RANKED/.test(em) ? '⏸️ كلانك عنده تحدٍّ مصنّف مؤجَّل — استئنفه أولاً'
: /ONE_RANKED_REQUEST/.test(em) ? '⏳ عندك طلب مصنّف بانتظار القبول — اسحبه أولاً أو انتظر منتهيه'
: /OPP_RANKED_BUSY/.test(em) ? '🏅 خصمك عنده تحدٍّ مصنّف لم يوثّق نتيجته بعد — اختر خصماً غيره'
: /RANKED_BUSY/.test(em) ? '🏅 عندك تحدٍّ مصنّف قائم — وثّق نتيجته أولاً'
: /SAME_OPPONENT/.test(em) ? '🤝 عندكم تحدٍّ غير مصنّف قائم مع هذا الكلان'
: /NO_FREE_SLOT/.test(em) ? '⏳ خاناتك ممتلئة — خلّص تحدياً أو اسحب طلباً'
: /NEED_MEMBERS/.test(em) ? ('⚠️ كلانك يحتاج '+(em.split(':')[1]||6)+' أعضاء على الأقل')
: /OPP_NO_SLOT/.test(em) ? '⚠️ خانات الخصم ممتلئة — جرّب كلاناً غيره'
: ('خطأ: '+em);
return;
}
$('chMsg').textContent = ranked?'⚡ تم إطلاق التحدي المصنّف!':'🤝 تم إطلاق تحدٍّ غير مصنّف!';
window.loadChallengesScreen();
/* يرجع للقائمة كي يرى طلبه بين الطلبات المعروضة */
setTimeout(function(){ if(document.getElementById('newch').classList.contains('active')){ go('challenges'); toast('⚡ انطلق طلبك — تلقاه بالطلبات المتاحة'); } }, 900);
});
},function(){ $('chMsg').textContent='تعذّر الاتصال — حاول مرة ثانية'; });
})();
});
});
// ===== إدارة الكلان (أعضاء · نائب · وصف · لوقو) =====
window.loadManageClan=function(){
if($('mgTransferBtn')) $('mgTransferBtn').hidden=true; window._pendingTransfer=null; if($('mgMsg')) $('mgMsg').textContent='';
window.authUser().then(function(res){ var user=res&&res.data&&res.data.user; if(!user){ go('account'); return; }
/* النائب والرئيس بالعضوية يحمّلان كلانهما — لا اشتراط «created_by».
نعتمد أولاً على my_lead_clan (تقرأ من clan_members الحيّ)، وإن لم
تجد شيئاً نرجع للاستعلام القديم كشبكة أمان لا أكثر. */
var _mgQ = sb.rpc('my_lead_clan',{p_user:user.id}).then(function(lr){
var cid=(lr&&lr.data)||null;
if(cid) return sb.from('clans').select('*').eq('id',cid).limit(1);
return sb.from('clans').select('*').or('created_by.eq.'+user.id+',vice_id.eq.'+user.id).neq('status','archived').limit(1);
});
_mgQ.then(function(cr){
var clan=cr.data&&cr.data[0]; if(!clan){ $('mgTitle').textContent='ما عندك كلان'; $('mgMembers').innerHTML=''; return; }
/* _myClanId يُعيَّن لكلان الإدارة الحالي حتى تعمل أزرار الإضافة والحفظ */
if(!window._myClanId) window._myClanId=clan.id;
$('mgTitle').textContent='إدارة CLAN '+clan.tag;
window._clanStatus=clan.status; window._clanRecruiting=!!clan.recruiting;
if(window.renderClanState) window.renderClanState(clan);
/* ═══ الحالاتُ هنا هي حالاتُ القاعدة نفسها ═══
كانت الواجهةُ تقفل على «accepted, live» والقاعدةُ تقفل على
خمس — فيرى صاحبُ التحدي المؤجَّل بابَه مفتوحاً، حتى إذا
ضغط رُدّ بخطأ لا يفهمه. القائمتان تتطابقان أو تكذب إحداهما. */
sb.from('challenges').select('id,challenge_results(id,status)').or('challenger_clan.eq.'+clan.id+',opponent_clan.eq.'+clan.id)
.in('status',['accepted','live','postponed','postpone_requested','resume_requested']).then(function(ac){
var locked=(ac.data||[]).filter(function(ch){
var results=(ch.challenge_results||[]);
var hasResult=results.some(function(r){ return ['pending','confirmed','verified','disputed'].indexOf(r.status)>-1; });
return !hasResult;
}).length>0; window._clanLocked=locked;
if($('mgLockNote')) $('mgLockNote').innerHTML = locked ? '
🔒 كلانك في تحدٍّ جارٍ — ما تقدر تعدّل معلومات الصفحة ولا تضيف أو تزيل أعضاء حتى يُوثَّق التحدي.
' : '';
if($('mgAddBtn')) $('mgAddBtn').style.opacity=locked?'.5':'1';
var dr=$('mgDoorRow');
if(dr && !dr._wired){ dr._wired=true; dr.addEventListener('click',function(){ var b=$('mgDoorBtn'); if(b) b.click(); }); }
if($('mgSaveInfo')) $('mgSaveInfo').style.opacity=locked?'.5':'1';
},function(){});
$('mgLogo').value=clan.logo_url||''; $('mgDesc').value=clan.description||'';
if($('mgLogoImg')){
if(clan.logo_url){ $('mgLogoImg').src=clan.logo_url; $('mgLogoImg').style.display='block'; if($('mgLogoInit')) $('mgLogoInit').style.display='none'; }
else { $('mgLogoImg').style.display='none'; if($('mgLogoInit')) $('mgLogoInit').style.display=''; }
}
/* الأرشفة لصاحب الكلان — لا لنائبه ولا لعضو */
var arcCard=$('mgArchiveCard'), arcBtn=$('mgArchiveBtn');
if(arcCard){
var owner=(clan.created_by===user.id);
arcCard.hidden=!owner;
if(owner && arcBtn && !arcBtn._wired){
arcBtn._wired=true;
arcBtn.addEventListener('click',function(){
if(window._clanLocked){
var mm2=$('mgArchMsg');
if(mm2){ mm2.style.color='var(--red)'; mm2.textContent='⚔️ عندكم تحدٍّ نشط — خلّصوه أولاً.'; }
return;
}
window.archiveClan(clan.id, clan.tag, window._mgMemCount||0);
});
}
}
if(window.loadJoinReqs) window.loadJoinReqs(clan.id);
sb.from('clan_members').select('id,role,member_name,psn,title,profile_id,profiles(username,psn_id)').eq('clan_id',clan.id).then(function(mr){
var rows=mr.data||[];
window._mgMemCount=rows.length;
var mc=document.getElementById('mgMemCount'); if(mc) mc.textContent=rows.length;
window._mgRows=rows; // يستعملها منتقي الخليفة
var _mn=window.chMinMembers(), _mx=clan.max_members||(window._appSet&&parseInt(window._appSet.clan_max_members,10))||20;
if(window.applyAddNote) window.applyAddNote();
if($('mgMemCount')) $('mgMemCount').textContent='('+rows.length+'/'+_mx+')'+(rows.length<_mn?' — تحتاج '+_mn+' للتحدي ⚠️':'');
var rb='background:rgba(56,182,255,.1);border:1px solid var(--line2);color:var(--blue2);font-family:inherit;font-size:10px;font-weight:800;padding:5px 8px;border-radius:7px;cursor:pointer';
var xb='background:rgba(255,107,94,.12);border:1px solid rgba(255,107,94,.3);color:var(--red);font-family:inherit;font-size:10px;font-weight:900;padding:5px 9px;border-radius:7px;cursor:pointer';
var gb='background:rgba(255,204,0,.14);border:1px solid rgba(255,204,0,.45);color:var(--gold);font-family:inherit;font-size:10px;font-weight:900;padding:5px 8px;border-radius:7px;cursor:pointer';
/* ═══ الكشف للبيانات، والرتب لصفحتها ═══
مكانان يغيّران الرتبة يعني كشفَين قد يختلفان — قرار الرتب
كله في «🎖️ الرتب والصلاحيات» حيث الشرح والتأكيد. */
var mkActs=function(m){
var top=(m.role==='leader'||m.role==='vice');
return '
'+
/* اللقب أُزيل من يد القائد — الإدارة وحدها تمنحه */
''+
/* الرئيس ما ينحذف — ينقل الرئاسة أولاً */
(m.role==='leader'?'':'')+
'
';
};
var miss=rows.filter(function(m){ return !mPsn(m); }).length;
var warn = miss ? '
'+
'⚠️ '+miss+' من أعضائك بلا آيدي سوني '+
'آيدي السوني هو هوية اللاعب — بدونه ما يرتبط حسابه بالكلان تلقائياً ولا يظهر للخصم عند التحدي. اضغط 🎮 PSN جنب كل عضو وأضفه.
' : '';
var rkHint='
'+
'🎖️ تغيير الرتب (رئيس · نائب · لاعب) من صفحة الرتب والصلاحيات
';
// نجلب اشتراك الكلان ونعيد بناء HTML لو تغيّر
sb.rpc('get_clan_subscription',{p_clan_id:clan.id}).then(function(sr){
window._myClanSub=sr.data||null;
if(window._myClanSub) $('mgMembers').innerHTML = rows.length? (warn+rkHint+hierarchyHTML(rows, mkActs)) : '
لا أعضاء بعد — أضف أعضاءك بأسمائهم
';
});
});
});
});
};
document.addEventListener('click',function(e){
var cb=e.target.closest&&e.target.closest('[data-coord]');
if(!cb||!window._myClanId) return;
var memberId=cb.getAttribute('data-coord');
var addCoord=cb.getAttribute('data-add')==='1';
cb.disabled=true;
sb.rpc('clan_set_coordinator',{p_clan_id:window._myClanId,p_member_id:memberId,p_add:addCoord}).then(function(r){
if(r.error||!r.data||!r.data.ok){
var why=r.data&&r.data.why;
if(why==='max_coordinators_reached') alert('وصلت للحد الأقصى من المنسقين ('+r.data.max+')');
else if(why==='no_subscription') alert('كلانك ما عنده باقة تدعم هذه الميزة');
else alert('تعذّر: '+(r.error?r.error.message:why||'خطأ'));
cb.disabled=false; return;
}
toast(addCoord?'👑 تم تعيين المنسق':'✅ تم إلغاء المنسق');
if(window.loadManageClan) window.loadManageClan();
});
});
if($('mgSaveInfo')) $('mgSaveInfo').addEventListener('click',function(){
if(!window._myClanId) return;
if(window._clanLocked){ $('mgMsg').textContent='🔒 الكلان في تحدي نشط — لا يمكن التعديل حتى ينتهي'; return; }
sb.from('clans').update({logo_url:($('mgLogo').value||'').trim(),description:($('mgDesc').value||'').trim()}).eq('id',window._myClanId).then(function(r){
$('mgMsg').textContent = r.error?(errAr(r.error.message)):'✅ حُفظت المعلومات'; });
});
if($('mgAddBtn')) $('mgAddBtn').addEventListener('click',function(){
if(!window._myClanId){ $('mgMsg').textContent='سجّل كلانك أولاً'; return; }
if(window._clanLocked){ $('mgMsg').textContent='🔒 الكلان في تحدي نشط — لا يمكن الإضافة حتى ينتهي'; return; }
/* آيدي السوني هو الهوية والاسم — خانة واحدة */
var psn=(($('mgAddPsn')||{}).value||'').trim();
if(!psn){ $('mgMsg').style.color='var(--red)'; $('mgMsg').textContent='🔑 اكتب آيدي السوني — هو هوية اللاعب'; return; }
if(psn.length<3||psn.length>24){ $('mgMsg').style.color='var(--red)'; $('mgMsg').textContent='⚠️ آيدي السوني من 3 إلى 24 حرفاً'; return; }
var name=psn;
var role='member'; /* الرتبة تُمنح بعد الانضمام من أزرار العضو */
$('mgMsg').style.color='var(--gold)'; $('mgMsg').textContent='...إضافة';
/* آيدي السوني ملك صاحبه: لو كان مسجّلاً بالمنصة تُرسل له دعوة ولا
يُضاف إلا بموافقته. غير المسجّل يُضاف كصفّ مبدئي ينتظر صاحبه. */
sb.rpc('add_clan_member',{p_clan:window._myClanId, p_psn:psn}).then(function(r){
var d=r&&r.data, m=$('mgMsg');
if(r&&r.error){ m.style.color='var(--red)'; m.textContent=(window.errAr?window.errAr(r.error.message):(errAr(r.error.message))); return; }
if(d&&d.ok){
if($('mgAddPsn')) $('mgAddPsn').value='';
m.style.color='var(--green,#3ddc84)';
m.textContent = d.act==='invited'
? ('📨 وصلت دعوة إلى «'+(d.name||psn)+'» — ينضم أول ما يوافق عليها')
: ('✅ أُضيف '+(d.name||psn)+' — يرتبط تلقائياً أول ما يسجّل بنفس الآيدي (وضع الاختبار)');
window.loadManageClan();
return;
}
var why=(d&&d.why)||''; m.style.color='var(--red)';
m.textContent =
why==='in_clan' ? ('⚠️ «'+psn+'» عضو في CLAN '+String(d.clan||'').toUpperCase()+' — لازم يخرج من كلانه أول')
: why==='not_registered' ? ('⚠️ «'+psn+'» ما عنده حساب بالمنصة — لازم يسجّل ويحفظ آيدي السوني حقه أول، وبعدها تضمّه')
: why==='psn_taken' ? ('⚠️ «'+psn+'» مسجّل في CLAN '+String(d.clan||'').toUpperCase()+' — لازم يُشال من كلانه أولاً')
: why==='name_taken' ? ('⚠️ الاسم «'+psn+'» محجوز لعضو آخر بالمنصة — تأكد من الآيدي')
: why==='banned' ? '🚫 هذا اللاعب محظور'
: why==='already' ? ('«'+psn+'» مضاف في كلانك مسبقاً')
: why==='invited' ? ('📨 دعوته مُرسَلة وينتظر ردّه')
: why==='self' ? 'هذا آيديك أنت'
: why==='full' ? '⚠️ الحد الأقصى 20 عضو'
: why==='psn_length' ? '⚠️ آيدي السوني من 3 إلى 24 حرفاً'
: why==='forbidden' ? '👑 ضمّ الأعضاء لرئيس الكلان ونائبه'
: why==='clan_busy' ? '⚔️ كلانك في تحدٍّ جارٍ — لا يُضاف أعضاء أثناء المباراة'
: 'تعذّرت الإضافة';
},function(){ $('mgMsg').style.color='var(--red)'; $('mgMsg').textContent='تعذّر الاتصال'; });
});
if($('mgTransferBtn')) $('mgTransferBtn').addEventListener('click',function(){
var t=window._pendingTransfer; if(!t){ return; }
var reason=prompt('سبب طلب النقل (يوصل للمشرفين):','رغبة اللاعب بالانتقال + عدم تجاوب رئيس كلانه الحالي');
if(reason===null) return;
window.authUser().then(function(res){ var u=res&&res.data&&res.data.user; if(!u) return;
sb.from('transfers').insert({player_name:t.name, from_clan:t.from_clan, to_clan:t.to_clan, reason:reason, requested_by:u.id, status:'pending'}).then(function(r){
if(r&&r.error){ $('mgMsg').style.color='var(--red)'; $('mgMsg').textContent=errAr(r.error.message); return; }
$('mgMsg').style.color='var(--green,#3ddc84)'; $('mgMsg').textContent='✅ أُرسل طلب النقل للمشرفين — بيراجعونه ويبلغونك';
if($('mgTransferBtn')) $('mgTransferBtn').hidden=true; window._pendingTransfer=null;
});
});
});
var mgS=$('manageclan');
if(mgS) mgS.addEventListener('click',function(e){
var t=e.target; if(!t.getAttribute) return;
var rmv=t.getAttribute('data-rmv'), mid=t.getAttribute('data-mid'), newRole=t.getAttribute('data-role');
var jracc=t.getAttribute('data-jracc'), jrrej=t.getAttribute('data-jrrej');
var titleId=t.getAttribute('data-title');
var psnId=t.getAttribute('data-psn');
if(psnId){
sb.from('clan_members').select('member_name,psn,profiles(psn_id)').eq('id',psnId).single().then(function(mr){
var d=mr.data||{};
var cur=(d.psn||(d.profiles&&d.profiles.psn_id)||'').trim();
var nm=d.member_name||'العضو';
/* ═══ آيدي السوني هوية لا حقل ═══
وعدنا اللاعب أنه لا يتغيّر — فرئيس الكلان ونائبه يُدخلانه أول
مرة لمن لا آيدي له، وتعديل القائم يمرّ بالإدارة وحدها. */
if(cur){
window.confirmBox({
icon:'🔒', tone:'gold',
title:'آيدي السوني لا يُعدَّل',
text:'آيدي «'+nm+'» هو '+cur+' — وهو هويته في المنصة.',
points:['وعدنا اللاعب أنه ثابت بعد الحفظ',
'به يضيفه الخصم وقت التحدي',
'تغييره يتم بمخاطبة الإدارة وحدها'],
yes:'📩 راسل الإدارة', no:'تمام'
}).then(function(ok){ if(ok) go('support'); });
return;
}
var v=prompt('آيدي السوني (PSN ID) لـ '+nm+':\n\n⚠️ تأكّد منه حرفاً حرفاً — ما ينعدّل بعد الحفظ.','');
if(v===null) return;
v=String(v).trim();
if(!v) return;
if(v.length<3||v.length>24){ toast('⚠️ آيدي السوني من 3 إلى 24 حرفاً'); return; }
window.confirmBox({
icon:'🎮', tone:'blue',
title:'احفظ «'+v+'» لـ'+nm+'؟',
text:'راجعه حرفاً حرفاً قبل الحفظ.',
points:['ما ينعدّل بعدها إلا عبر الإدارة','به يضيفه الخصم وقت التحدي'],
yes:'✔ احفظه', no:'راجعه'
}).then(function(ok){
if(!ok) return;
sb.from('clan_members').update({psn:v}).eq('id',psnId).then(function(r){
if(r&&r.error){ toast(errAr(r.error.message)); return; }
toast('🎮 حُفظ آيدي السوني'); window.loadManageClan();
});
});
});
return;
}
/* منح الألقاب انتقل للإدارة — والقاعدة ترفضه من غير الطاقم */
if(window._clanLocked && (rmv || (mid&&newRole) || jracc)){ toast('🔒 الكلان في تحدي نشط — لا يمكن تعديل الأعضاء'); return; }
if(jracc){
sb.from('join_requests').select('*').eq('id',jracc).single().then(function(jr){
var j=jr.data; if(!j) return;
sb.from('clan_members').select('id',{count:'exact',head:true}).eq('clan_id',j.clan_id).then(function(cc){
if((cc.count||0)>=20){ toast('⚠️ الكلان وصل 20 عضو'); return; }
sb.from('clan_members').insert({clan_id:j.clan_id, member_name:j.username, psn:j.psn_id, profile_id:j.user_id, role:'member'}).then(function(ir){
if(ir.error && !/duplicate|unique/i.test(ir.error.message||'')){ toast('خطأ بالإضافة'); }
sb.from('join_requests').update({status:'accepted'}).eq('id',jracc).then(function(){
sb.from('transfers').insert({player_name:j.username,from_clan:null,to_clan:j.clan_id,status:'approved',approved_at:new Date().toISOString(),requested_by:j.user_id}).then(function(){},function(){});
toast('✅ أُضيف '+(j.username||'اللاعب')+' لكلانك'); window.loadManageClan();
});
});
});
});
return;
}
if(jrrej){ sb.from('join_requests').delete().eq('id',jrrej).then(function(){ toast('رُفض طلب الانضمام'); window.loadManageClan(); }); return; }
if(rmv){ sb.from('clan_members').select('member_name,clan_id,role,profiles(username)').eq('id',rmv).single().then(function(mr){ var m=mr.data;
if(m&&m.role==='leader'){ toast('👑 ما ينحذف رئيس الكلان — سلّم الرئاسة لعضو آخر أولاً'); return; }
/* ═══ الطرد قرارٌ لا زلّة إصبع ═══ نشرح أثره قبل وقوعه */
var _nm=(m&&((m.profiles&&m.profiles.username)||m.member_name))||'هذا العضو';
window.confirmBox({
icon:'🚪', tone:'danger',
title:'طرد «'+_nm+'» من الكلان؟',
text:'هذا الإجراء يسري فوراً.',
points:['يخرج من كشف الكلان في الحال',
'يُسجَّل انتقاله في سوق الانتقالات',
'تقدر تعيده لاحقاً بآيدي السوني نفسه'],
yes:'🚪 اطرده', no:'تراجع'
}).then(function(ok){
if(!ok) return;
sb.from('clan_members').delete().eq('id',rmv).then(function(rr){
if(rr&&rr.error){ toast(/LEADERLESS/i.test(rr.error.message||'')?'👑 ما ينحذف رئيس الكلان — سلّم الرئاسة أولاً':(errAr(rr.error.message))); return; }
var _kname=(m&&((m.profiles&&m.profiles.username)||m.member_name))||'';
if(_kname) sb.from('transfers').insert({player_name:_kname, from_clan:m.clan_id, to_clan:null, status:'pending', requested_by:window._uid}).then(function(){},function(){});
toast('🚪 طُرد '+_nm); window.loadManageClan();
});
});
}); return; }
if(mid && newRole && window._myClanId){
var rows=window._mgRows||[];
var tgt=rows.filter(function(m){return m.id===mid;})[0]||{};
// ترقية عضو لرئيس = تسليم رئاسة كامل، ننبّه قبلها
if(newRole==='leader'){
var nm=mName(tgt);
if(!tgt.profile_id){ toast('⚠️ '+nm+' ما سجّل بالمنصة بعد — الرئيس لازم يكون له حساب'); return; }
if(!confirm('تسليم رئاسة الكلان إلى «'+nm+'»؟\n\nأنت تصير لاعباً عادياً، وهو يصير صاحب الصلاحيات الكاملة.')) return;
window.applyRole(mid,'leader',null); return;
}
window.applyRole(mid,newRole,null);
}
});
/* ═══════════ صفحة الرتب ═══════════
مكانٌ واحد لتعيين المنازل: من يقود، ومن ينوب، ومن يلعب — وإزالةُ
عضو لا تتمّ بضغطة واحدة، فالخطأ فيها مكلف. */
window._rkRows=[]; window._rkMid=null;
window.rkClose=function(){
var s=document.getElementById('rkSheet'), b=document.getElementById('rkBack');
var a=document.getElementById('rkmAsk');
if(a){ a.hidden=true; a.innerHTML=''; } /* لا يبقى تحذيرٌ قديم لعضوٍ آخر */
if(s) s.hidden=true; if(b) b.hidden=true; window._rkMid=null;
};
window.loadRanks=function(){
var box=document.getElementById('rkList'); if(!box) return;
var cid=window._myClanId||window._myMemberClanId;
if(!cid){ box.innerHTML='
ما عندك كلان
'; return; }
box.innerHTML='
⏳ جاري التحميل...
';
sb.from('clan_members').select('id,role,member_name,psn,title,profile_id,profiles(username,psn_id)')
.eq('clan_id',cid).then(function(mr){
var rows=mr.data||[]; window._rkRows=rows;
var ORD={leader:0,vice:1,coordinator:2,member:3};
rows.sort(function(a,b){
var d=(ORD[a.role]==null?3:ORD[a.role])-(ORD[b.role]==null?3:ORD[b.role]);
return d || String(mName(a)).localeCompare(String(mName(b)));
});
var cnt=document.getElementById('rkCount'); if(cnt) cnt.textContent='('+rows.length+')';
/* من يملك التعيين: الرئيس والنائب معاً — المنسّق والأعضاء يشاهدون */
var me=(rows.filter(function(m){ return m.profile_id===window._uid; })[0]||{});
var boss=(me.role==='leader'||me.role==='vice')||!!window._isStaff;
window._rkBoss=boss;
var note=document.getElementById('rkNote');
if(note) note.innerHTML = boss
? '
اضغط على أي عضو لتغيير رتبته أو لقبه أو لإزالته.
'
: '
🔒 تعيين الرتب لرئيس الكلان وحده — هذه للاطّلاع.
';
var B={leader:['l','👑 رئيس'],vice:['v','🎖️ نائب'],coordinator:['c','📋 منسق كلان'],member:['m','🎮 لاعب']};
box.innerHTML = rows.length ? rows.map(function(m){
var nm=mName(m), psn=mPsn(m);
var b=B[m.role]||B.member;
var cls=(m.role==='leader')?' lead':(m.role==='vice'?' vice':(m.role==='coordinator'?' coord':''));
return '';
}).join('') : '
لا أعضاء بعد
';
});
};
window.rkOpen=function(mid){
if(!window._rkBoss){ toast('🔒 تعيين الرتب لرئيس الكلان'); return; }
var m=(window._rkRows||[]).filter(function(x){ return x.id===mid; })[0]; if(!m) return;
if(window._clanLocked){ toast('🔒 الكلان في تحدي نشط — لا يمكن تعديل الأعضاء'); return; }
window._rkMid=mid;
var nm=mName(m), psn=mPsn(m);
document.getElementById('rkmAv').textContent=String(nm).slice(0,1).toUpperCase();
document.getElementById('rkmName').textContent=nm;
document.getElementById('rkmSub').textContent=psn?('🎮 '+psn):'بلا آيدي سوني';
var opt=function(role,ic,ttl,dsc){
var on=(m.role===role);
return '';
};
/* ═══ رتبة النائب ═══ تُفعَّل وتُطفأ من لوحة التحكم (feat_vice).
مطفأةً نُخفي خيارها — إلا لعضوٍ هو نائبٌ فعلاً، فنُظهره كي يستطيع
الرئيس إنزاله بدل أن يبقى نائباً محبوساً بلا سبيلٍ لتغييره. */
var viceOn=((window._appSet||{}).feat_vice==='1') || (m.role==='vice');
var h='
الرتبة
'+
opt('leader','👑','رئيس الكلان','تسليم الرئاسة كاملة — وأنت تصير لاعباً عادياً')+
(viceOn?opt('vice','🎖️','نائب الرئيس','يطلق التحديات ويردّ عليها ويستقبل طلبات الانضمام — بلا مسّ الرتب'):'')+
opt('member','🎮','لاعب','يمثّل الكلان في المباريات')+
(window._myClanSub&&(m.role==='member'||m.role==='coordinator')?
opt('coordinator','📋','منسق كلان','يدير طلبات التحديات ويوثّق نتائجها نيابةً عن الرئيس — متاح لأصحاب باقة GOLD وDIAMOND'):'')+
'
بياناته
'+
/* اللقب يُعرض ولا يُفتح — منحُه صار بيد الإدارة وحدها */
'
🏷️اللقب'+
(m.title?(esc(m.title)+' · تمنحه الإدارة'):'بلا لقب — يُطلب من الإدارة بتذكرة')+'
'+
/* المضاف مقفول: قفلٌ بلا سهم — الشكل يقول إنه لا يُفتح قبل أن يُضغط */
'';
if(m.role!=='leader'){
h+='
خطر
'+
''+
'
'+
'
تأكيد الإزالة: سيخرج '+esc(nm)+' من كشف الكلان فوراً. تقدر تضيفه لاحقاً بآيدي السوني نفسه.
'+
'
'+
''+
''+
'
';
} else {
h+='
👑 الرئيس لا يُزال — سلّم الرئاسة لعضو آخر أولاً، ثم أزِله إن شئت.
';
}
document.getElementById('rkmBody').innerHTML=h;
document.getElementById('rkSheet').hidden=false;
document.getElementById('rkBack').hidden=false;
};
(function(){
var sec=document.getElementById('ranks');
if(sec) sec.addEventListener('click',function(e){
var b=e.target.closest?e.target.closest('[data-rk]'):null;
if(b) window.rkOpen(b.getAttribute('data-rk'));
});
var sh=document.getElementById('rkSheet');
if(sh) sh.addEventListener('click',function(e){
var t=e.target.closest?e.target.closest('[data-rkrole],[data-rktitle],[data-rkpsn],[data-rkrmv],[data-rkyes],[data-rkno]'):null;
if(!t) return;
var mid=window._rkMid; if(!mid) return;
var m=(window._rkRows||[]).filter(function(x){ return x.id===mid; })[0]||{};
var nm=mName(m);
var role=t.getAttribute('data-rkrole');
if(role){
if(role===m.role) return;
if(role==='leader'){
if(!m.profile_id){ toast('⚠️ '+nm+' ما سجّل بالمنصة — الرئيس لازم يكون له حساب'); return; }
if(!confirm('تسليم رئاسة الكلان إلى «'+nm+'»؟\n\nأنت تصير لاعباً عادياً، وهو يصير صاحب الصلاحيات الكاملة.')) return;
}
/* منسق كلان — يستخدم دالة خاصة */
if(role==='coordinator'||m.role==='coordinator'){
var addCoord=(role==='coordinator');
sb.rpc('clan_set_coordinator',{p_clan_id:window._myClanId,p_member_id:m.profile_id,p_add:addCoord}).then(function(r){
if(r.error||!r.data||!r.data.ok){
var why=r.data&&r.data.why;
if(why==='max_coordinators_reached') toast('وصلت للحد الاقصى من المنسقين ('+r.data.max+')');
else if(why==='no_subscription') toast('كلانك ما عنده باقة تدعم هذه الميزة');
else toast('تعذر');
return;
}
toast(addCoord?'تم تعيين منسق الكلان':'تم إلغاء منسق الكلان');
if(window.rkClose) window.rkClose();
window.loadRanks();
});
return;
}
/* لا تغلق الورقة قبل ان تعرف الجواب */
window.applyRole(mid, role, null, true);
return;
}
if(t.getAttribute('data-rktitle')){
/* بقي الشرط حارساً لو وصل نقرٌ من نسخةٍ قديمة مخزَّنة */
toast('🏷️ الألقاب تمنحها الإدارة — افتح تذكرة واطلب اللقب');
return;
}
if(t.getAttribute('data-rkpsn')){
/* ═══ آيدي السوني هوية لا حقل ═══
القائم لا يُفتح له مربّع أصلاً — لا نُظهر باباً مغلقاً. */
var cur=mPsn(m)||'';
if(cur){
window.confirmBox({
icon:'🔒', tone:'gold',
title:'آيدي السوني لا يُعدَّل',
text:'آيدي «'+nm+'» هو '+cur+' — وهو هويته في المنصة.',
points:['وعدنا اللاعب أنه ثابت بعد الحفظ',
'به يضيفه الخصم وقت التحدي',
'تغييره يتم بمخاطبة الإدارة وحدها'],
yes:'📩 راسل الإدارة', no:'تمام'
}).then(function(ok){ if(ok){ window.rkClose(); go('support'); } });
return;
}
var v=prompt('آيدي السوني لـ«'+nm+'»:\n\n⚠️ تأكّد منه حرفاً حرفاً — ما ينعدّل بعد الحفظ.','');
if(v===null) return;
v=String(v).trim().slice(0,24);
if(!v) return;
if(v.length<3){ toast('⚠️ آيدي السوني من 3 إلى 24 حرفاً'); return; }
window.confirmBox({
icon:'🎮', tone:'blue',
title:'احفظ «'+v+'» لـ'+nm+'؟',
text:'راجعه حرفاً حرفاً قبل الحفظ.',
points:['ما ينعدّل بعدها إلا عبر الإدارة','به يضيفه الخصم وقت التحدي'],
yes:'✔ احفظه', no:'راجعه'
}).then(function(ok){
if(!ok) return;
sb.from('clan_members').update({psn:v}).eq('id',mid).then(function(r){
if(r&&r.error){ toast(errAr(r.error.message)); return; }
toast('🎮 حُفظ آيدي السوني'); window.rkClose(); window.loadRanks();
});
});
return;
}
if(t.getAttribute('data-rkrmv')){ var cf=document.getElementById('rkConfirm'); if(cf) cf.hidden=false; return; }
if(t.getAttribute('data-rkno')){ var cf2=document.getElementById('rkConfirm'); if(cf2) cf2.hidden=true; return; }
if(t.getAttribute('data-rkyes')){
if(m.role==='leader'){ toast('👑 ما ينحذف رئيس الكلان'); return; }
sb.from('clan_members').delete().eq('id',mid).then(function(rr){
if(rr&&rr.error){ toast(/LEADERLESS/i.test(rr.error.message||'')?'👑 ما ينحذف رئيس الكلان — سلّم الرئاسة أولاً':errAr(rr.error.message)); return; }
var _rkname=(m.profiles&&m.profiles.username)||m.member_name||'';
if(_rkname) sb.from('transfers').insert({player_name:_rkname, from_clan:m.clan_id||window._myClanId, to_clan:null, status:'pending', requested_by:window._uid}).then(function(){},function(){});
toast('أُزيل '+nm+' من الكلان'); window.rkClose(); window.loadRanks();
if(window.loadManageClan) window.loadManageClan();
});
}
});
document.addEventListener('keydown',function(e){ if(e.key==='Escape') window.rkClose(); });
})();
/* ═══ الرئاسة لا تُترك شاغرة ═══
الرئيس ما ينزّل نفسه نائباً أو لاعباً إلا بتسمية من يخلفه، وإلا صار
الكلان بلا رئيس (والقاعدة نفسها ترفض العملية). */
/* ═══ أين يُقال الردّ؟ ═══
الرتبة تُغيَّر من ورقة «الرتب والصلاحيات»، فكان الرفض يُرسم في
حاوية صفحة إدارة الكلان — والورقة تُغلق قبله، فيظهر التحذير في
شاشةٍ أخرى بعيداً عن موضع الفعل. الآن الردّ يتبع مصدره: من الورقة
يبقى فيها والورقة لا تُغلق حتى ينجح الأمر. */
window.applyRole=function(mid, role, successor, inSheet){
(async function(){
var sess = await sb.auth.getSession();
var token = sess && sess.data && sess.data.session && sess.data.session.access_token;
var res, r;
try {
res = await fetch(sb.supabaseUrl + '/rest/v1/rpc/set_member_role', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': sb.supabaseKey,
'Authorization': 'Bearer ' + (token || sb.supabaseKey)
},
body: JSON.stringify({ p_member: mid, p_role: role, p_successor: successor||null })
});
var data = await res.json();
r = res.ok ? { data: data, error: null } : { data: null, error: { message: (data && data.message) || 'خطأ' } };
} catch(e) {
r = { data: null, error: { message: e.message } };
}
(function(r){
var d=r&&r.data;
var host=inSheet?'rkmAsk':'mgRoleAsk';
var say=function(m){
var b=$(host);
if(!inSheet||!b){ toast(m); return; }
b.hidden=false;
b.innerHTML='
'+esc(m)+'
';
};
if(r&&r.error){ say(errAr(r.error.message)); return; }
if(d&&d.ok){
['mgRoleAsk','rkmAsk'].forEach(function(k){ var b=$(k); if(b){ b.hidden=true; b.innerHTML=''; } });
if(inSheet && window.rkClose) window.rkClose();
toast(d.act==='handover'||d.act==='stepdown'
? ('👑 انتقلت الرئاسة'+(d.new_leader?(' إلى '+d.new_leader):'')+' — أنت الآن لاعب')
: d.act==='vice' ? '🎖️ صار نائب الرئيس'
: 'صار لاعب');
window.loadManageClan(); if(window.loadRanks) window.loadRanks();
if(window.reloadProfile) window.reloadProfile();
return;
}
var why=(d&&d.why)||'';
if(why==='need_successor'){ window.askSuccessor(mid, role, (d&&d.eligible)||0, inSheet); return; }
say( why==='no_account' ? '⚠️ لازم يكون العضو مسجّلاً بالمنصة'
: why==='forbidden' ? '⚠️ رئيس الكلان وحده يغيّر الرتب'
: why==='self' ? '⚠️ اختر عضواً غيرك'
: why==='no_vice' ? '⚠️ رتبة النائب مطفأة حالياً'
: why==='leader_to_vice' ? '⚠️ سلّم الرئاسة أولاً — الرئيس لا يُنزَّل نائباً مباشرةً'
: why==='bad_successor' ? '⚠️ العضو المختار ليس من كلانك'
: 'تعذّرت العملية');
})(r);
})();
};
/* بطاقة اختيار الخليفة — تُرسم حيث بدأ الأمر */
window.askSuccessor=function(mid, role, eligible, inSheet){
var box=$(inSheet?'rkmAsk':'mgRoleAsk'); if(!box) return;
/* لكل شاشةٍ صفوفها: الورقة تقرأ _rkRows وصفحة الإدارة _mgRows */
var src=(inSheet ? (window._rkRows||window._mgRows) : (window._mgRows||window._rkRows))||[];
var rows=src.filter(function(m){ return m.id!==mid && m.profile_id; });
box.hidden=false;
var shut='var b=this.closest(\'#rkmAsk,#mgRoleAsk\');if(b){b.hidden=true;b.innerHTML=\'\'}';
if(!rows.length){
box.innerHTML='
'+
'⚠️ ما تقدر تنزّل نفسك الآن '+
'الكلان ما يصير بلا رئيس، ولازم من يخلفك يكون مسجّلاً بالمنصة بنفس آيدي السوني. '+
'خلّ أحد أعضائك يسجّل حسابه ويوثّق PSN، وبعدها سلّمه الرئاسة.'+
'
ما يصير الكلان بلا رئيس. اختر من يستلم الرئاسة، وأنت تنزل لاعباً مباشرة.
'+
''+
'
'+
''+
''+
'
';
var ok=$('mgSuccOk'), no=$('mgSuccNo');
if(no) no.addEventListener('click',function(){ box.hidden=true; box.innerHTML=''; });
if(ok) ok.addEventListener('click',function(){
var sel=$('mgSucc'); var sid=sel&&sel.value; if(!sid) return;
var nm=(sel.options[sel.selectedIndex]||{}).text||'';
if(!confirm('تسليم الرئاسة إلى «'+nm+'»؟\n\nصلاحيات إدارة الكلان تنتقل له بالكامل.')) return;
ok.disabled=true; window.applyRole(mid, role, sid, inSheet);
});
box.scrollIntoView({behavior:'smooth',block:'nearest'});
};
// ===== لوحة الإدارة =====
window.loadAdminTransfers=function(){
var box=$('adTransfers'); if(!box) return;
sb.from('clans').select('id,tag').then(function(cm){
var CM={}; (cm.data||[]).forEach(function(c){CM[c.id]=c.tag;});
sb.from('transfers').select('*').eq('status','pending').order('id',{ascending:false}).then(function(r){
var rows=r.data||[];
box.innerHTML = rows.length? rows.map(function(t){
return '
🔄 نقل: '+esc(t.player_name||'لاعب')+'
من CLAN '+esc(String(CM[t.from_clan]||'؟').toUpperCase())+' إلى CLAN '+esc(String(CM[t.to_clan]||'؟').toUpperCase())+''+(t.reason?' 📝 '+esc(t.reason):'')+'
';
}).join('') : '
لا طلبات نقل ✅
';
},function(){ box.innerHTML='
لا طلبات نقل
'; });
});
};
function approveTransfer(id){
sb.from('transfers').select('*').eq('id',id).single().then(function(tr){
var t=tr.data; if(!t) return;
sb.from('clan_members').delete().eq('clan_id',t.from_clan).ilike('member_name',t.player_name).then(function(){
sb.from('clan_members').select('id',{count:'exact',head:true}).eq('clan_id',t.to_clan).then(function(cc){
if((cc.count||0)>=20){ toast('⚠️ الكلان الجديد وصل 20 عضو'); return; }
sb.from('clan_members').insert({clan_id:t.to_clan, member_name:t.player_name, role:'member'}).then(function(){
window.authUser().then(function(res){ var u=res&&res.data&&res.data.user;
sb.from('transfers').update({status:'approved', approved_by:u?u.id:null, approved_at:new Date().toISOString()}).eq('id',id).then(function(){
if(t.requested_by) sb.from('notifications').insert({user_id:t.requested_by, title:'✅ اعتُمد طلب النقل', body:t.player_name+' انتقل لكلانك رسمياً'}).then(function(){},function(){});
toast('✅ تم اعتماد النقل — ظهر في سوق الانتقالات'); window.loadAdmin();
});
});
});
});
});
});
}
function rejectTransfer(id){
window.authUser().then(function(res){ var u=res&&res.data&&res.data.user;
sb.from('transfers').select('requested_by,player_name').eq('id',id).single().then(function(tr){
sb.from('transfers').update({status:'rejected', approved_by:u?u.id:null}).eq('id',id).then(function(){
var rq=tr.data&&tr.data.requested_by; if(rq) sb.from('notifications').insert({user_id:rq, title:'🔄 طلب النقل مرفوض', body:'رُفض طلب نقل '+((tr.data&&tr.data.player_name)||'اللاعب')}).then(function(){},function(){});
toast('رُفض طلب النقل'); window.loadAdmin();
});
});
});
}
window.loadAdmin=function(){
window.authUser().then(function(res){ var user=res&&res.data&&res.data.user;
if(!user){ if($('adminGate'))$('adminGate').hidden=false; if($('adminBody'))$('adminBody').hidden=true; return; }
sb.from('profiles').select('role').eq('id',user.id).single().then(function(pr){
var role=(pr.data&&pr.data.role)||'member'; var staff=(role==='admin'||role==='moderator'); window._adminRole=role;
if($('adminGate')) $('adminGate').hidden=staff; if($('adminBody')) $('adminBody').hidden=!staff;
if(!staff) return;
var isAdmin=(role==='admin');
if($('adRoleBanner')) $('adRoleBanner').innerHTML = isAdmin ? '
🛡️ أنت أدمن — كامل الصلاحيات
' : '
🧑⚖️ أنت مشرف — صلاحياتك: اعتماد الكلانات + حل البلاغات فقط (الرتب والحذف للأدمن)
';
if($('adUsersHdr')) $('adUsersHdr').style.display = isAdmin?'':'none';
var bB='background:rgba(56,182,255,.1);border:1px solid var(--line2);color:var(--blue2);font-family:inherit;font-size:10px;font-weight:800;padding:6px 10px;border-radius:8px;cursor:pointer';
var bG='background:rgba(61,220,132,.14);border:1px solid rgba(61,220,132,.4);color:var(--green);font-family:inherit;font-size:10px;font-weight:800;padding:6px 10px;border-radius:8px;cursor:pointer';
var bX='background:rgba(255,107,94,.12);border:1px solid rgba(255,107,94,.3);color:var(--red);font-family:inherit;font-size:10px;font-weight:800;padding:6px 10px;border-radius:8px;cursor:pointer';
sb.from('clans').select('id,tag,status').order('created_at',{ascending:false}).then(function(cr){
var rows=cr.data||[];
$('adClans').innerHTML= rows.length? rows.map(function(c){
var st=c.status==='ratified'?'✔ معتمد':'⏳ بانتظار الاعتماد';
var btn=c.status==='ratified'?'':'';
return '
'+esc((c.tag||'?').slice(0,2))+'
CLAN '+esc(c.tag)+'
'+st+'
'+btn+(role==='admin'?'':'')+'
';
}).join('') : '
لا كلانات
';
});
if(role==='admin'){
sb.from('profiles').select('id,username,role,psn_id').limit(80).then(function(ur){
var rows=ur.data||[];
$('adUsers').innerHTML= rows.length? rows.map(function(u){
var rb=u.role==='admin'?'⚙️ أدمن':(u.role==='moderator'?'🧑⚖️ مشرف':(u.role==='leader'?'👑 قائد':'🎮 عضو'));
var psn=u.psn_id?(' · PSN: '+esc(u.psn_id)):' · لا PSN';
return '
🔒 إدارة رتب الأعضاء متاحة للأدمن فقط. صلاحياتك كمشرف: اعتماد الكلانات + حل البلاغات.
'; }
sb.from('clans').select('id,tag').then(function(cm){
var CM={}; (cm.data||[]).forEach(function(c){CM[c.id]=c.tag;});
sb.from('reports').select('*, challenges(challenger_clan,opponent_clan)').eq('status','open').order('created_at',{ascending:false}).then(function(rr){
var reps=rr.data||[];
if(!reps.length){ if($('adReports')) $('adReports').innerHTML='
لا بلاغات مفتوحة ✅
'; return; }
var rids=reps.map(function(x){return x.reporter_id;}).filter(Boolean);
sb.from('profiles').select('id,username').in('id',rids).then(function(pm){
var UM={}; (pm.data||[]).forEach(function(p){UM[p.id]=p.username;});
if($('adReports')) $('adReports').innerHTML=reps.map(function(x){
var ch=x.challenges||{}; var vs=ch.challenger_clan?('CLAN '+String(CM[ch.challenger_clan]||'؟').toUpperCase()+' VS CLAN '+String(CM[ch.opponent_clan]||'؟').toUpperCase()):'—';
var pu=x.evidence_url||'', low=pu.split('?')[0].toLowerCase();
var media = !pu ? '
';
}).join('');
},function(){});
},function(){});
},function(){});
});
});
};
var adS=$('admin');
if(adS) adS.addEventListener('click',function(e){ var t=e.target; if(!t.getAttribute) return;
var ra=t.getAttribute('data-ratify'), un=t.getAttribute('data-unratify'), del=t.getAttribute('data-delclan');
var role=t.getAttribute('data-setrole'), uid=t.getAttribute('data-uid');
if(ra){ sb.rpc('staff_set_clan_status',{p_cid:ra,p_status:'ratified'}).then(function(r){ if(r.error){toast(errAr(r.error.message));return;} toast('✔ اعتُمد الكلان'); window.loadAdmin(); }); }
else if(un){ sb.rpc('staff_set_clan_status',{p_cid:un,p_status:'pending'}).then(function(r){ if(r.error){toast(errAr(r.error.message));return;} toast('أُلغي الاعتماد'); window.loadAdmin(); }); }
else if(del){ if(window._adminRole!=='admin'){ toast('🔒 حذف الكلانات للأدمن فقط'); return; } sb.from('clans').delete().eq('id',del).then(function(r){ if(r.error){toast(errAr(r.error.message));return;} toast('حُذف الكلان'); window.loadAdmin(); }); }
else if(role&&uid){ if(window._adminRole!=='admin'){ toast('🔒 تغيير الرتب للأدمن فقط'); return; } sb.rpc('admin_set_role',{p_uid:uid,p_role:role}).then(function(r){ if(r.error){toast(errAr(r.error.message));return;} toast('✅ تم تحديث الرتبة'); window.loadAdmin(); }); }
else if(t.getAttribute('data-repok')){ var w1=prompt('سبب القبول (يوصل للمُبلِّغ):','مخالفة صحيحة — النقطة للطرف المتضرر'); if(w1!==null) adminResolveReport(t.getAttribute('data-repok'),'✔ مقبول: '+(w1||'مخالفة صحيحة')); }
else if(t.getAttribute('data-repno')){ var w2=prompt('سبب الرفض (يوصل للمُبلِّغ):','بلاغ غير صحيح'); if(w2!==null) adminResolveReport(t.getAttribute('data-repno'),'✕ مرفوض: '+(w2||'بلاغ غير صحيح')); }
else if(t.getAttribute('data-trok')){ if(confirm('اعتماد النقل؟ سيُنقل اللاعب فعلياً من كلانه الحالي للكلان الجديد.')) approveTransfer(t.getAttribute('data-trok')); }
else if(t.getAttribute('data-trno')){ rejectTransfer(t.getAttribute('data-trno')); }
});
function adminResolveReport(id,resolution){
sb.rpc('staff_resolve_report',{p_rid:id, p_res:resolution}).then(function(r){
if(r.error){ toast(errAr(r.error.message)); return; }
toast('✅ تم البتّ في البلاغ'); window.loadAdmin();
});
}
// ===== الرئيسية (ديناميكية من الإعدادات/القاعدة) =====
/* ═══ سؤال واحد لا عشرة ═══
كانت الرئيسية تسأل القاعدة عشرة أسئلة عند كل تحديث، من كل جهاز —
فلمّا دخل الناس دفعةً واحدة ليلة الافتتاح صارت عاصفةً أسقطت البوّابة.
الآن سيرفرنا يسأل القاعدة سؤالاً واحداً كل ثوانٍ ويحفظ الجواب،
وكل الأجهزة تقرأ منه — ألف زائرٍ كعشرة. وإن تعذّر، الطريق القديم قائم. */
/* ═══ الإدارة متصلة الآن ═══ يعتمد على _pres (يتحدث كل 60 ثانية أصلاً) */
window._staffList=null;
window.loadStaffOnline=function(){
var box=$('staffOnlineList'), wrap=$('staffOnlineWrap');
if(!box||!wrap) return;
var render=function(){
var list=(window._staffList||[]).filter(function(p){ return window.isOnline(p.id); });
if(!list.length){ wrap.style.display='none'; return; }
wrap.style.display='block';
var titleEl=$('staffOnlineTitle');
if(titleEl) titleEl.textContent='الإدارة متصلة الآن — '+list.length;
box.innerHTML=list.map(function(p){
var isAdmin=p.role==='admin', isLead=p.isLead;
/* ═══ اللونُ المخصَّصُ يسبقُ الثابت ═══
كان اللونانِ مكتوبَينِ في الشيفرة، فمن غيّرَ لونَه في اللوحةِ
رآه في كلِّ مكانٍ إلّا هنا. **إعدادٌ يظهرُ في تسعةِ مواضعَ ويغيبُ
في العاشرِ يبدو عطلاً لا استثناء.** فيُقرأُ من `fxOf` أوّلاً،
والثابتُ احتياطٌ لمن لا نمطَ له. */
var _sfx=(window.fxOf&&window.fxOf(p.id))||null;
var color=(_sfx&&_sfx.color)||(isAdmin?'#ff6b5e':'#3ddc84');
var colorDark=(_sfx&&_sfx.color)||(isAdmin?'#b8291d':'#1f8f57');
var _sBadge=(_sfx&&_sfx.badge_on&&_sfx.badge_icon)||null;
var icon=isAdmin?'👑':(isLead?'⭐':'🛡️');
var ini=String(p.username||'?').slice(0,2).toUpperCase();
var mins=Math.max(0, Math.round((Date.now()-(window._pres[p.id]||Date.now()))/60000));
var since=mins<1?'الآن':('منذ '+mins+'د');
var ring='';
var av=p.avatar_url
? ''
: '
'+
/* ═══ اللقبُ الشخصيُّ هنا أيضاً ═══
كان النصُّ ثابتاً «أدمن/مشرف»، فمن مُنِحَ لقباً رآه في الدردشةِ
وحدَها. **وصفةٌ تظهرُ في موضعٍ وتغيبُ في آخرَ تبدو عطلاً لا
تمييزاً** — والشارةُ تتبعُ صاحبَها أينما ظهر. */
/* ═══ الشارةُ تُبنى من مصدرِها لا تُعادُ كتابتُها ═══
كتبتُ هنا نصّاً خامّاً بلونٍ فقط، **فضاعت الحبّةُ والحدُّ
والحركةُ التي يمنحُها `fxBadge`** — فبدت الشارةُ في هذا الموضعِ
غيرَها في الدردشةِ والملفّ. **ما يُنسَخُ يتباعد؛ وما يُنادى
يبقى واحداً.** */
'
'; return; }
var features={'name_color':'لون خاص لاسمك','rank_badge':'شارة رتبة مميزة','priority_tournaments':'اولوية في البطولات','custom_rank_name':'اسم رتبة من اختيارك','ffa_priority':'مقعد محجوز في FFA','max_members':'زيادة عدد الاعضاء','clan_badge':'شارة الكلان','coordinator_role':'منسق كلان','member_gold_badge':'شارة لجميع الاعضاء','multi_coordinators':'منسقون متعددون','custom_color':'لون خاص للكلان'};
var html='
';
rows.forEach(function(x){
var isDiamond=x.id.indexOf('diamond')>-1;
var tc=isDiamond?'#b9f2ff':'#FFD700';
var gc=isDiamond?'linear-gradient(135deg,rgba(185,242,255,.15),rgba(100,200,255,.05))':'linear-gradient(135deg,rgba(255,215,0,.15),rgba(255,140,0,.05))';
var bc=isDiamond?'rgba(185,242,255,.35)':'rgba(255,215,0,.35)';
var badge=isDiamond?'DIAMOND':'GOLD';
var perks=x.perks||{};
var ph='';
Object.keys(perks).forEach(function(k){
if(!perks[k]||!features[k]) return;
var txt=features[k];
if(k==='max_members') txt='زيادة الاعضاء الى '+perks[k];
ph+='
v'+txt+'
';
});
var img=x.image_url?'':'
'+(isDiamond?'D':'G')+'
';
html+='
'+
'
'+img+
''+
'
'+badge+'
'+
'
'+x.price_sar+' ر.س/ شهر
'+
'
'+
'
'+
'
'+x.name_ar+'
'+
(x.description?'
'+x.description+'
':'')+
'
'+ph+'
'+
''+
'
';
});
html+='
';
box.innerHTML=html;
};
function loadStoreBody(){
var box=$('storeBody'); if(!box) return;
sb.from('store_items').select('*').eq('active',true).order('sort_order').then(function(r){
window._storeItems=r.data||[];
window.renderStoreItems();
},function(){ box.innerHTML='
تعذّر التحميل
'; });
};
/* ═══ القوانين تُبنى من القاعدة ═══ */
window.loadRules=function(){
var box=document.getElementById('rlBody'); if(!box) return;
if(window._rulesLoaded) return;
Promise.all([
sb.from('rl_sections').select('*').eq('active',true).order('sort_order'),
sb.from('rl_items').select('*').eq('active',true).order('sort_order')
]).then(function(r){
var secs=(r[0]&&r[0].data)||[], items=(r[1]&&r[1].data)||[];
if(!secs.length){ box.innerHTML='
لا قوانين بعد
'; return; }
var byS={};
items.forEach(function(it){ (byS[it.section_id]=byS[it.section_id]||[]).push(it); });
var html='';
secs.forEach(function(sec){
html+='
';
cSet(id, bx.innerHTML);
};
window.rzlRender(f.mteam||[], lm); /* بكرةُ النتائج بدلَ القائمة */
window.dzlRender(f.mduel||[]); /* بكرةُ الأفرادِ بدلَ القائمة */
if(window.loadPromos) window.loadPromos();
window._renderHomeTop5(f.top5||[]);
if(window.codarBootStep) window.codarBootStep('home');
setTimeout(function(){ ['homePodium','homeClansRest','homePlayers','homeChallenges','homeDuels'].forEach(function(id){ if($(id)&&$(id).innerHTML&&$(id).innerHTML.indexOf('جاري التحميل')===-1) cSet(id,$(id).innerHTML); }); }, 1200);
};
/* الطريق القديم — احتياطٌ إن غابت الخلاصة */
window.loadHomeDirect=function(){
getSettings().then(function(m){
if(!$('hSeason')) return;
window._hSeasonClan=null; /* بلا متصدّر ⇒ الضغط يفتح الدوري */
if(m.season_name){ $('hSeason').textContent=m.season_name+(m.days_left?(' · متبقٍ '+m.days_left+' يوم'):''); }
else { // بدل نص ثابت: نعرض المتصدّر الحقيقي — والضغط يفتح صفحته
sb.from('clans').select('id,tag,points').eq('status','ratified').order('points',{ascending:false}).limit(1).then(function(r){
var c=r.data&&r.data[0], lead=(c&&(c.points||0)>0);
window._hSeasonClan = lead ? c.id : null;
$('hSeason').textContent = lead ? ('👑 المتصدّر: CLAN '+String(c.tag)) : 'الأساطير رجعوا 🔥';
},function(){ $('hSeason').textContent='الأساطير رجعوا 🔥'; });
}
});
/* عمود محدَّد لا * — البريد والواتساب محجوبان عن القراءة العامة */
sb.from('profiles').select('id',{count:'exact'}).limit(1).then(function(r){ setCount('hPlayers',r); });
sb.from('challenges').select('*',{count:'exact',head:true}).then(function(r){ setCount('hCh',r); });
sb.rpc('count_completed_challenges').then(function(r){
if(r&&!r.error&&r.data!=null) setCount('hChCompleted',{count:r.data});
});
sb.from('challenges').select('status').in('status',['open','accepted','live']).then(function(r){ var rows=r.data||[], op=0, lv=0; rows.forEach(function(c){ if(c.status==='open')op++; else lv++; }); setCount('hOpen',{count:op}); setCount('hLive',{count:lv}); },function(){});
var _bc=(window._boot&&window._boot.clans); if(window._boot) window._boot.clans=null;
(_bc? _bc.then(function(d){return {data:d};}) : sb.from('clans').select('id,tag,type,wins,losses,points,streak').neq('status','archived').order('points',{ascending:false})).then(function(r){
window._renderHomeClans(r.data||[]);
/* النتائج الموثّقة — نفس بطاقة السجل حرفياً: عشرُ كلانات وعشرُ أفراد */
Promise.all([
sb.from('match_log').select('*').eq('kind','team').order('verified_at',{ascending:false}).limit(10),
sb.from('match_log').select('*').eq('kind','duel').order('verified_at',{ascending:false}).limit(10),
sb.from('clans').select('id,logo_url')
]).then(function(rs2){
var lm={}; ((rs2[2]&&rs2[2].data)||[]).forEach(function(c){ lm[c.id]=c; });
var draw=function(id, rows, emp){
var bx=$(id); if(!bx) return;
bx.innerHTML = rows.length
? rows.map(function(m){ return window.matchCard(window.matchNorm(m, lm)); }).join('')
: '
'+emp+'
';
cSet(id, bx.innerHTML);
};
window.rzlRender((rs2[0]&&rs2[0].data)||[], lm);
window.dzlRender((rs2[1]&&rs2[1].data)||[]);
},function(){});
});
/* الاحتياطُ يسحبُ اللوحةَ نفسَها لا `profiles` — فلا يختلفُ رقمٌ */
sb.rpc('ffa_board',{p_limit:5}).then(function(r){
window._renderHomeTop5(((r.data||{}).rows)||[]);
if(window.codarBootStep) window.codarBootStep('home');
});
setTimeout(function(){ ['homePodium','homeClansRest','homePlayers','homeChallenges','homeDuels'].forEach(function(id){ if($(id)&&$(id).innerHTML&&$(id).innerHTML.indexOf('جاري التحميل')===-1) cSet(id,$(id).innerHTML); }); }, 1200);
};
/* أزرار نمط العرض — تُربط مرةً واحدة */
window._wireHomeStyle=function(){
var hs=$('homeStyle');
if(hs && !hs._wired){
hs._wired=true;
hs.addEventListener('click',function(e){
var b=e.target.closest('button'); if(!b) return;
[].forEach.call(hs.querySelectorAll('button'),function(x){x.classList.remove('on')});
b.classList.add('on');
window._homeStyle=b.getAttribute('data-v');
try{ cSet('homePodium',''); cSet('homeClansRest',''); }catch(e2){}
if(window.loadHome) window.loadHome();
});
}
};
/* منصّة الكلانات — من أي مصدرٍ جاءت الصفوف */
window._renderHomeClans=function(rowsAll){
window._homeClansAll=rowsAll;
var hs=window._homeStyle||'sniper';
if((window._appSet||{}).hide_rush==='1') hs='sniper';
var rows=rowsAll.filter(function(c){
if(c.type==='rush') return hs==='rush';
if(c.type==='sniper') return hs==='sniper';
return true;
});
var box=$('homePodium');
setCount('hClans',{count:rows.length});
var map={}; rows.forEach(function(c){ map[c.id]=c.tag; }); window._homeClanMap=map;
if(box){
if(!rows.length){ box.innerHTML='
لا كلانات بعد — سجّل كلانك 👑
'; var rb0=$('homeClansRest'); if(rb0) rb0.innerHTML=''; }
else {
var podHTML=function(c,rank){ if(!c) return '';
var pos=rank===1?'pod--1':(rank===2?'pod--2':'pod--3'), hexc=rank===1?'h-gold':(rank===2?'h-silver':'h-bronze');
var crown=rank===1?'
♛
':'', prc=rank===1?'pr pr--1':'pr';
var pt=typeBadge(c.type);
var hasStreak=c.streak&&c.streak>=2;
var nameTag=hasStreak
? ''+esc(c.tag)+''+c.streak+''
: esc(c.tag);
var cardExtra=hasStreak?' style="cursor:pointer;border-color:rgba(255,140,0,.35);box-shadow:0 0 18px rgba(255,100,0,.18)"':' style="cursor:pointer"';
return '
'+crown+'
'+rank+'
'+
/* التاقُ مطبوعٌ في السطرِ الذي يلي مباشرةً، فلا هويّةَ تضيعُ
باستبدالِ السداسيّة. وألوانُ الميداليّةِ تبقى للبطاقةِ لأنّها
تقولُ الموقعَ، والشارةُ تقولُ الرتبةَ — معنيانِ لا يُدمَجان. */
(window.RANKS_ON
? '
'+window.rkPointsPin(c.points, rank===1?64:52)+'
'
: '
'+esc((c.tag||'?').slice(0,4))+'
')+
'
'+nameTag+'
'+pt+'
'+(c.wins||0)+'–'+(c.losses||0)+'
'+(c.points||0)+'
';
};
box.innerHTML = podHTML(rows[1],2)+podHTML(rows[0],1)+podHTML(rows[2],3); cSet('homePodium', box.innerHTML);
var rest=rows.slice(3,10), rb=$('homeClansRest');
if(rb){ rb.innerHTML = rest.map(function(c,i){
var hasStreak=c.streak&&c.streak>=2; var pt=typeBadge(c.type);
var nameStr=hasStreak
? 'CLAN '+esc(String(c.tag||''))+''+c.streak+''
: 'CLAN '+esc(String(c.tag||''));
var rowStyle=hasStreak
? 'display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--panel);border:1px solid rgba(255,130,0,.35);border-radius:11px;margin-bottom:6px;cursor:pointer;box-shadow:0 0 10px rgba(255,100,0,.1)'
: 'display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--panel);border:1px solid var(--line);border-radius:11px;margin-bottom:6px;cursor:pointer';
return '
';
};
var nameOf=function(p){
var f=window.fxOf?window.fxOf(p.id):null;
return f?window.fxName(f,p.username||'لاعب'):esc(p.username||'لاعب');
};
var fire=function(p){ return p.streak>=2?' 🔥'+p.streak:''; };
/* رقمانِ في خانةٍ واحدةٍ ينقلبانِ في سطرٍ عربيّ — فيُلزَمانِ ltr */
var wl=function(p){ return ''+p.w+'–'+p.l+''; };
var c=rows[0];
var champ='
';
cSet('homePlayers',box.innerHTML);
};
/* ═══ شبكة الأمان ═══
حتى لو أخفق البثّ لسببٍ لم نتوقّعه، الصفحة المفتوحة أمامك تُحدَّث
كل نصف دقيقة بهدوء — فلا تبقى تنتظر شيئاً لن يصل. */
/* كل جهازٍ يختار فترته عشوائياً (٤٥–٩٠ ثانية) — فلا تتزامن الأجهزة
فتطرق القاعدة دفعةً واحدة كما حدث ليلة الافتتاح */
setInterval(function(){
if(document.hidden || !window._uid) return;
var s0=document.querySelector('.screen.active'); var id=s0?s0.id:'';
if(id==='challenges' || id==='chchat' || id==='home' || id==='notifs'){
try{ _refreshActiveNow(); }catch(e){}
}
}, 45000+Math.floor(Math.random()*45000));
// ===== تحديث فوري لكل شي (Realtime) =====
function _refreshActiveNow(){
/* بطاقة التحدي المفتوحة تُحدَّث هي أيضاً — القبول يظهر بلا إغلاق */
try{ var _sh=document.getElementById('chSheet');
if(_sh && !_sh.hidden && window._chsId && window.goChallenge) window.goChallenge(window._chsId);
}catch(e){}
var s=document.querySelector('.screen.active'); var id=s?s.id:'';
if(id==='clans'&&window.loadClans) window.loadClans();
else if(id==='challenges'&&window.loadChallengesScreen) window.loadChallengesScreen();
else if(id==='manageclan'&&window.loadManageClan) window.loadManageClan();
else if(id==='admin'&&window.loadAdmin) window.loadAdmin();
else if(id==='home'&&window.loadHome) window.loadHome();
else if(id==='solo'&&window.loadSolo) window.loadSolo();
/* صفحة الكلان المفتوحة: أعضاؤها ونتائجها تتحدّث حيّاً */
else if(id==='clan'&&window._viewClan&&window.loadClanDetail) window.loadClanDetail(window._viewClan);
}
/* ═══ حدٌّ أدنى بين إعادتَي تحميل ═══
كان المهدّئ ١٥٠ مللي — وهو ليس حدّاً أدنى بل تأجيلٌ قصير: كل حدثٍ
لحظي يُعيد تحميل الشاشة كاملةً (نحو أحد عشر استعلاماً)، فيبلغ
ذلك ستّ مرّاتٍ في الثانية للمستخدم الواحد. وبعشرين متصلاً يصير
المجموع ألف استعلامٍ في الثانية — فتُرهَق القاعدة وتسقط بوّابة
الـAPI متقطّعةً، ويقف الدخول عند «جاري الدخول».
كان يكفي حين كنّا اثنين نجرّب، فلمّا جاء الناس انكسر.
الآن حدٌّ أدنى حقيقي: أول حدثٍ يُنفَّذ بعد ٦٠ مللي فيبدو فورياً،
وما تلاه خلال المهلة يُجمع في إعادةٍ واحدة عند حدّها. */
/* ═══ ولا تُعاد شاشةٌ لحدثٍ لا يخصّها ═══
كان أيُّ تغيّرٍ في أيّ جدولٍ يُعيد بناء الشاشة المفتوحة أياً كانت:
عضوٌ يبدّل رتبته في كلانٍ بعيد فتُعاد شاشة الدوري عند الجميع.
هذه خريطةُ ما يعني كلَّ شاشة — وما عداه يُهمَل بلا كلفة. */
/* «profiles» أُخرجت من الرئيسية عمداً: نبضة الحضور تحدّث صفّ العضو
كل دقيقة، فكانت كل نبضةٍ من أي عضوٍ تُعيد بناء الرئيسية عند الجميع —
بعشرة استعلاماتٍ لكل جهاز. هذا ما أسقط البوّابة ليلة الافتتاح.
تغيّر الصدارة يصل عبر match_log و challenge_results فلا نفقد شيئاً. */
var SCREEN_CARES={
home: ['clans','challenges','challenge_results','match_log'],
clans: ['clans','challenge_results','match_log'],
clan: ['clans','clan_members','challenge_results','match_log'],
challenges: ['challenges','challenge_bids','challenge_results','clans'],
manageclan: ['clan_members','join_requests','clans','clan_invites','clan_offers'],
solo: ['duels','duel_results','duel_bids'],
admin: ['clans','challenges','challenge_results','reports','join_requests','transfers']
};
var _raTimer=null, _raLast=0, RA_GAP=1500;
function refreshActive(tbl){
if(document.hidden) return; /* بالخلفية: لا شغل */
if(tbl){
var s=document.querySelector('.screen.active'), id=s?s.id:'';
var cares=SCREEN_CARES[id];
if(cares && cares.indexOf(tbl)===-1) return; /* لا يخصّها */
}
if(_raTimer) return; /* موعدٌ قائم — يكفي عنه */
var since=Date.now()-_raLast;
var wait=(since>=RA_GAP) ? 60 : (RA_GAP-since);
_raTimer=setTimeout(function(){
_raTimer=null; _raLast=Date.now();
if(!document.hidden) _refreshActiveNow();
}, wait);
}
window.refreshActive=refreshActive; /* المهدَّأة — يناديها من يريد تحديثاً غير عاجل */
// تحديث بيانات الشاشة الحالية + فحص تحديث نسخة التطبيق
window.refreshNow=function(){ try{ _refreshActiveNow(); }catch(e){} if(navigator.serviceWorker&&navigator.serviceWorker.getRegistration){ navigator.serviceWorker.getRegistration().then(function(reg){ if(reg) reg.update(); },function(){}); } };
// فحص دوري للتحديثات (تُطبّق خلال دقيقة بدون ما يقفل التطبيق)
setInterval(function(){
if(document.hidden) return; /* بصمة البناء تكفي — هذا احتياط بعيد */
if(navigator.serviceWorker&&navigator.serviceWorker.getRegistration){
navigator.serviceWorker.getRegistration().then(function(reg){ if(reg) reg.update(); },function(){});
}
}, 300000);
// اسحب الشاشة لتحت للتحديث (Pull to refresh)
(function(){
var startY=0, pulling=false, TH=72, ind=null;
function getInd(){ if(!ind){ ind=document.createElement('div'); ind.id='ptr'; ind.style.cssText='position:fixed;top:0;left:0;right:0;text-align:center;font-size:12.5px;font-weight:800;color:var(--blue2);padding:10px;transform:translateY(-46px);transition:transform .2s;z-index:200;pointer-events:none'; document.body.appendChild(ind); } return ind; }
function sTop(){ return window.scrollY || document.documentElement.scrollTop || (document.body&&document.body.scrollTop) || 0; }
document.addEventListener('touchstart',function(e){ pulling = sTop()<=2; if(pulling) startY=e.touches[0].clientY; },{passive:true});
document.addEventListener('touchmove',function(e){ if(!pulling) return; var dy=e.touches[0].clientY-startY; if(dy>4){ var i=getInd(); i.style.transform='translateY('+(Math.min(dy,110)-46)+'px)'; i.textContent=dy>TH?'↻ حرّر للتحديث':'↓ اسحب للتحديث'; } },{passive:true});
document.addEventListener('touchend',function(e){ if(!pulling) return; pulling=false; var dy=e.changedTouches[0].clientY-startY; if(dy>TH){ var i=getInd(); i.textContent='...جاري التحديث'; i.style.transform='translateY(8px)'; if(window.refreshNow) window.refreshNow(); setTimeout(function(){ i.style.transform='translateY(-46px)'; },1000); } else if(ind){ ind.style.transform='translateY(-46px)'; } },{passive:true});
})();
var _chatPresence=null;
function initChatPresence(){
if(_chatPresence) return;
_chatPresence = sb.channel('chat-room',{config:{presence:{key: window._uid||('g'+Math.floor(Math.random()*1e6))}}});
_chatPresence.on('presence',{event:'sync'},function(){ var st=_chatPresence.presenceState(); var el=$('chatOnline'); if(el) el.textContent=Object.keys(st||{}).length; })
.subscribe(function(status){ if(status==='SUBSCRIBED') _chatPresence.track({at:Date.now()}); });
}
function chatRoleBadge(role,isLeader){
var s='font-size:9px;font-weight:800;padding:1px 6px;border-radius:20px;white-space:nowrap;';
if(role==='admin') return '🛡️ إداري';
if(role==='moderator') return '🧑⚖️ مشرف';
if(isLeader) return '👑 رئيس كلان';
return '🎮 لاعب';
}
function chatMsgHTML(m,owners){
var own = m.user_id && m.user_id===window._uid;
var pr=m.profiles||{};
var badge=window.fxBadge(window.fxOf(m.user_id)) || chatRoleBadge(pr.role, owners[m.user_id]);
var nm=(pr.username||m.username||'لاعب');
var ini=esc(String(nm).slice(0,1).toUpperCase());
// لو الصورة انكسرت (روابط قديمة منتهية) نرجع للحرف الأول بدل علامة الاستفهام
var av = pr.avatar_url
? ''
: ''+ini+'';
var time=tHM(m.created_at);
var fx=window.fxOf(m.user_id,pr.style_id);
/* ═══ شريط المقتبَس ═══ نصُّه مخزَّنٌ مع الرسالة، فرسمُه بلا استعلام */
var quo='';
if(m.reply_body){
var gone=!m.reply_to; /* أصلُها حُذف */
quo='
');
/* زرّ الردّ: صغيرٌ باهتٌ بجانب الفقاعة — يظهر دائماً فيُعرف، ولا
يزاحم النصّ. الملصقات لا يُردّ عليها (لا نصَّ يُقتبس). */
var rbtn=(m.id && m.kind!=='sticker')
? '' : '';
/* حذف الرسالة — لحامل مفتاح إشراف الدردشة، والقاعدة تفحصه ثانيةً */
if(m.id && window._perms && window._perms.mod_chat===true)
rbtn += '';
return '
'+av+
'
'+nameEl+badge+''+time+'
'+
quo+bubble+'
'+rbtn+'
';
}
/* ═══════════ بطاقة اللاعب من الدردشة ═══════════
كانت لوحاً يطلع من الأسفل يحمل أزراراً وحدها — لا وجه ولا رتبة
ولا نقاط. وبطاقة المشرف في غرفة التحدي كانت أوضح: صورةٌ كبيرة
ووسامٌ ولقب. فوحّدناهما على شكل البطاقة، وحملناها أفعال اللوح
كي لا نكسب المنظر ونخسر العمل. */
window.closeChatUser=function(){
var c=$('modCard'), b=$('modBack');
/* نزع المفوَّض مع المحتوى — وإلا بقي معلّقاً حين تفتح بطاقة المشرف */
if(c){ c.hidden=true; c.innerHTML=''; c.onclick=null; } if(b) b.hidden=true;
var s=$('cuSheet'), ob=$('cuBack'); if(s) s.hidden=true; if(ob) ob.hidden=true;
};
window.chatUser=function(uid,name){
if(!uid) return;
var c=$('modCard'), b=$('modBack'); if(!c||!b) return;
var me=(uid===window._uid);
window._cuUid=uid; window._cuName=name||'لاعب';
var draw=function(p){
p=p||{};
var nm=p.username||name||'لاعب';
var ini=esc(String(nm).slice(0,1).toUpperCase());
/* كتمٌ سارٍ؟ الماضي لا يُعرض — المدّة تنتهي وحدها */
var mut=(p.chat_muted_until && Date.parse(p.chat_muted_until)>Date.now()) ? p.chat_muted_until : null;
var av=p.avatar_url
? ''
: '
'+ini+'
';
var R={admin:['🛡️ إداري','rgba(255,107,94,.16)','rgba(255,107,94,.45)','#ff8c7a'],
moderator:['🧑⚖️ مشرف','rgba(255,204,0,.14)','rgba(255,204,0,.45)','var(--gold)']};
var r=R[p.role];
var fx=window.fxOf(uid,p.style_id);
var nmHTML=fx?window.fxName(fx,nm):esc(nm);
/* سطر الأرقام: نقاطه وسجلّه — به يُقاس الخصم قبل تحدّيه */
var stat=function(v,l,col){
return '
'+v+''+
''+l+'
'; };
var act=function(id,ic,tx,cls){
return ''; };
c.innerHTML=av+
'
':'')+
act('profile','👤','الملف الشخصي','go2')+
(me?'':act('reply','↩︎','رُدّ عليه في الدردشة'))+
act('dm','💬',me?'صندوق رسائلي':'رسالة خاصة')+
(me?'':act('duel','🎯','تحدَّه فردياً'))+
(me?'':act('friend','🤝','أضفه صديقاً'))+
/* ═══ أدوات الإشراف ═══ تظهر لحامل مفتاح mod_chat وحده،
والقاعدة ترفض من يتجاوزها. ولا تُعرض على النفس ولا الطاقم. */
((!me && window._perms && window._perms.mod_chat===true
&& p.role!=='admin' && p.role!=='moderator')
? '
'+
'
أدوات الإشراف
'+
(mut
? '
🔇 مكتومٌ حتى '+esc(tHM(mut))+' '+esc(tDay(mut))+'
'+
act('unmute','🔊','ارفع الكتم')
: act('mute','🔇','اكتمه عن الدردشة'))+
'
'
: '')+
'';
/* الأفعال بمفوَّضٍ واحد لا onclick في كل زر — أسلم مع الأسماء
التي فيها علامة اقتباس، وأخفّ على المتصفّح */
c.onclick=function(e){
var t=e.target&&e.target.closest?e.target.closest('[data-cua]'):null; if(!t) return;
var a=t.getAttribute('data-cua'), u=window._cuUid, n=window._cuName;
window.closeChatUser();
if(a==='profile') return window.openPlayer(u);
if(!window._uid && a!=='profile') return go('account');
if(a==='reply'){ var i=$('chatInput'); if(i){ i.value='@'+n+' '+(i.value||''); i.focus(); } return; }
if(a==='dm') return me?go('inbox'):window.openDM(u,n);
if(a==='duel') return window.challengePlayer(n);
if(a==='friend') return window.addFriend(u);
if(a==='mute'){
var mins=prompt('كم دقيقة يُكتم «'+n+'» عن الدردشة؟\n\n٦٠ = ساعة · ١٤٤٠ = يوم · ١٠٠٨٠ = أسبوع','60');
if(mins===null) return;
var why=prompt('السبب (يصله ويُقيَّد في السجل):','إساءة في الدردشة');
if(why===null) return;
return sb.rpc('mod_mute_chat',{p_uid:u,p_minutes:parseInt(mins,10)||60,p_reason:why||null})
.then(function(r){
var d=r&&r.data;
if(r&&r.error) return toast('تعذّر: '+r.error.message);
if(!d||!d.ok){
var W={forbidden:'🔒 تحتاج مفتاح إشراف الدردشة',self:'ما تكتم نفسك',
staff_immune:'🛡️ لا يُكتم من الطاقم',owner_immune:'👑 المالك محصَّن',
not_found:'ما لقيت العضو'};
return toast(W[d&&d.why]||'تعذّر الكتم');
}
toast('🔇 كُتم '+d.username+' — '+d.minutes+' دقيقة');
},function(){ toast('تعذّر الاتصال'); });
}
if(a==='unmute'){
return sb.rpc('mod_unmute_chat',{p_uid:u}).then(function(r){
var d=r&&r.data;
if(r&&r.error) return toast('تعذّر: '+r.error.message);
if(!d||!d.ok) return toast(d&&d.why==='forbidden'?'🔒 تحتاج مفتاح إشراف الدردشة':'تعذّر');
toast('🔊 رُفع الكتم عن '+d.username);
},function(){ toast('تعذّر الاتصال'); });
}
};
c.hidden=false; b.hidden=false;
};
draw({username:name}); /* نرسمها فوراً بما نعرف، ثم نُتمّها */
sb.from('profiles').select('username,avatar_url,psn_id,title,points,wins,losses,role,style_id,bio,chat_muted_until')
.eq('id',uid).single().then(function(r){ if(r&&r.data) draw(r.data); },function(){});
};
function updateChatBadge(){
var n=window._chatUnread||0;
var b=document.getElementById('moreNavBadge');
if(b){ if(n>0){ b.textContent=n>99?'99+':String(n); b.style.display=''; } else b.style.display='none'; }
['chatMoreBadge','dwChatBadge'].forEach(function(id){ var e=document.getElementById(id); if(!e) return;
if(n>0){ e.textContent=n>99?'99+':String(n); e.hidden=false; } else e.hidden=true; });
if(window.updateMenuBadge) window.updateMenuBadge();
}
// شارة ☰ في الترويسة = مجموع غير المقروء (دردشة + رسائل + تحديات)
window.updateMenuBadge=function(){
var n=(window._chatUnread||0)+(window._dmUnread||0)+(window._chUnread||0), b=document.getElementById('menuBadge');
if(!b) return;
if(n>0){ b.textContent=n>99?'99+':String(n); b.style.display=''; } else b.style.display='none';
};
/* ===== عدّاد التحديات الجديدة — نفس نمط الدردشة ===== */
window.updateChBadge=function(){
var n=window._chUnread||0;
['dwChBadge','chNavBadge'].forEach(function(id){
var e=document.getElementById(id); if(!e) return;
if(n>0){ e.textContent=n>99?'99+':String(n); e.hidden=false; e.style.display=''; }
else { e.hidden=true; e.style.display='none'; }
});
if(window.updateMenuBadge) window.updateMenuBadge();
};
window.initChBadge=function(){
var raw=null; try{ raw=localStorage.getItem('cx_chSeen'); }catch(e){}
// تثبيت جديد: نعتبر القائم حالياً مقروءاً — العدّاد يبدأ من الجديد
if(raw===null){
try{ localStorage.setItem('cx_chSeen', new Date().toISOString()); }catch(e){}
window._chUnread=0; return window.updateChBadge();
}
/* يعدّ كل ما يستدعي تصرّفاً: تحديات مفتوحة · موجّهة لكلاني · فردية موجّهة لي */
var jobs=[ sb.from('challenges').select('id',{count:'exact',head:true})
.in('status',['open','pending']).gt('created_at',raw) ];
if(window._uid){
jobs.push(sb.from('duels').select('id',{count:'exact',head:true})
.eq('opponent_id',window._uid).eq('status','pending').gt('created_at',raw));
}
Promise.all(jobs).then(function(rs){
window._chUnread=rs.reduce(function(a,r){ return a+((r&&r.count)||0); },0);
window.updateChBadge();
},function(){});
};
window.clearChBadge=function(){
try{ localStorage.setItem('cx_chSeen', new Date().toISOString()); }catch(e){}
window._chUnread=0; window.updateChBadge();
};
window.initChatBadge=function(){
var raw=null; try{ raw=localStorage.getItem('cx_chatSeen'); }catch(e){}
// أول تشغيل / تثبيت جديد: نعتبر كل القديم مقروء — العدّاد يبدأ من الرسائل الجديدة فقط
if(raw===null){
sb.from('messages').select('id').order('id',{ascending:false}).limit(1).then(function(r){
var last=(r.data&&r.data[0]&&r.data[0].id)||0;
try{ localStorage.setItem('cx_chatSeen', String(last)); }catch(e){}
window._chatUnread=0; updateChatBadge();
},function(){ window._chatUnread=0; updateChatBadge(); });
return;
}
var seen=parseInt(raw||'0',10);
sb.from('messages').select('id',{count:'exact',head:true}).gt('id',seen).then(function(r){ window._chatUnread=r.count||0; updateChatBadge(); },function(){});
};
/* ═══════════ الدردشة: المتصلون · الإيموجي · الملصقات ═══════════ */
var EMOJIS=['😂','🤣','😎','😤','😭','😱','🥶','🤯','😴','🥱','😏','🙄','😅','🤔','🫡','🤫','😈','👿','🤡','💀','☠️','👻',
'❤️','🔥','💥','⚡','✨','💯','🎯','🏆','🥇','🎖️','👑','💪','👊','✌️','👍','👎','🙏','👏','🤝','🖐️',
'🎮','🕹️','🔫','🔭','💣','🛡️','⚔️','🗡️','🚀','🏹','🥷','🪖','😡','🤬','😳','🥵','🤢','🤮','🫠','😵'];
var STK_EMO=['🔥','💀','😂','🏆','⚔️','🎯','👑','💪','🫡','😎','🤣','😭','☠️','🥇','💣','🚀','😤','🥶','🤯','👏','🖐️','🤝','😱','🎮'];
window._stkPack=null;
window.emoClose=function(){
var b=$('emoBack'), sh=$('emoSheet');
if(b) b.hidden=true; if(sh) sh.hidden=true;
};
function emoRender(tab){
var g=$('emoGrid'), n=$('emoNote'); if(!g) return;
[].forEach.call(document.querySelectorAll('.emo__tabs button'),function(b){
b.classList.toggle('on', b.getAttribute('data-emot')===tab);
});
if(tab==='emoji'){
g.className='emo__grid';
g.innerHTML=EMOJIS.map(function(e){ return ''; }).join('');
if(n) n.textContent='';
return;
}
g.className='emo__grid stk';
/* ملصقات كود آر المخصّصة أولاً — والكبيرة من الإيموجي حتى تصل */
var packs=(window._stkPack||[]);
g.innerHTML=
packs.map(function(u){ return ''; }).join('')+
STK_EMO.map(function(e){ return ''; }).join('');
if(n) n.textContent=packs.length?'':'🎨 ملصقات COD·AR الخاصة قادمة — وهذه تسدّ الجوع';
}
window.openEmo=function(){
if($('emoBack')) $('emoBack').hidden=false;
if($('emoSheet')) $('emoSheet').hidden=false;
emoRender('emoji');
if(window._stkPack===null){
window._stkPack=[];
fetch('/api/stickers').then(function(r){return r.json();}).then(function(d){
if(d&&d.ok&&Array.isArray(d.stickers)) window._stkPack=d.stickers;
}).catch(function(){});
}
};
window.sendSticker=function(emoji, url){
if(window._chatOff){ toast('الدردشة موقوفة مؤقتاً'); return; }
if(!window._uid){ toast('سجّل دخولك أول'); go('account'); return; }
window.emoClose();
sb.from('profiles').select('username').eq('id',window._uid).single().then(function(pr){
sb.from('messages').insert({user_id:window._uid, username:(pr.data&&pr.data.username)||'لاعب',
body:emoji||'🎨', kind:'sticker', media_url:url||null}).then(function(r){
if(r&&r.error){ toast('تعذّر الإرسال'); return; }
if(window.loadChat) window.loadChat();
});
});
};
(function(){
var b=$('chatEmo'); if(b) b.addEventListener('click', window.openEmo);
var sh=$('emoSheet'); if(!sh) return;
/* iOS والكيبورد مفتوح يُسقط نقرة click على أزرار اللوح (يصرف اللمسة على
إدارة التركيز وشريطه العائم) — فنلتقط رفعة الإصبع نفسها ونمنع الافتراضي:
الكيبورد يثبت والإيموجي ينكتب من أول لمسة. click تبقى للماوس. */
function emoAct(t){
if(!t||!t.closest) return false;
var tab=t.closest('[data-emot]');
if(tab){ emoRender(tab.getAttribute('data-emot')); return true; }
var eb=t.closest('[data-emo]');
if(eb){
var i=$('chatInput');
if(i){ i.value=(i.value||'')+eb.getAttribute('data-emo'); }
window.emoClose();
if(i){ try{ i.focus(); }catch(e){} }
return true;
}
var st=t.closest('[data-stk]');
if(st){ window.sendSticker(st.getAttribute('data-stk'), null); return true; }
var si=t.closest('[data-stkimg]');
if(si){ window.sendSticker('', si.getAttribute('data-stkimg')); return true; }
return false;
}
var tx0=null, ty0=null;
sh.addEventListener('touchstart',function(e){
var t=e.touches&&e.touches[0]; if(!t){ tx0=null; return; }
tx0=t.clientX; ty0=t.clientY;
},{passive:true});
sh.addEventListener('touchend',function(e){
if(tx0===null) return;
var t=e.changedTouches&&e.changedTouches[0]; if(!t){ tx0=null; return; }
var moved=Math.abs(t.clientX-tx0)>10 || Math.abs(t.clientY-ty0)>10; tx0=null;
if(moved) return; /* تمريرٌ في الشبكة لا نقرة */
if(emoAct(e.target)) e.preventDefault(); /* يلغي نقرة الشبح ويثبّت الكيبورد */
},{passive:false});
sh.addEventListener('click',function(e){ emoAct(e.target); });
document.addEventListener('keydown',function(e){ if(e.key==='Escape') window.emoClose(); });
})();
/* ═══ شريط المتصلين: وجوهٌ تنبض فوق الدردشة ═══ */
window._cbarCache={};
window.renderChatBar=function(){
var bar=$('chatBar'); if(!bar) return;
var ids=Object.keys(window._pres||{}).filter(function(id){ return window.isOnline(id); });
var cnt=$('chatOnline'); if(cnt) cnt.textContent=ids.length;
if(!ids.length){
bar.innerHTML='
🌙 ما فيه أحد متصل الآن — كن أول الحاضرين
';
return;
}
var need=ids.filter(function(id){ return !window._cbarCache[id]; });
var draw=function(){
/* العدّاد يتصدّر الصفّ، ثم الوجوه — وزرٌّ لا div، ليُفتح باللمس وبالمفتاح */
bar.innerHTML='
'+ids.length+'في الميدان
'+
ids.slice(0,20).map(function(id){
var p=window._cbarCache[id]||{};
var nm=p.username||'لاعب';
var ltr=esc(String(nm).slice(0,1).toUpperCase());
var av=p.avatar_url
? ''
: ''+ltr+'';
return '';
}).join('')+
(ids.length>20?'
+'+(ids.length-20)+'
':'');
};
if(need.length){
sb.from('profiles').select('id,username,avatar_url').in('id',need.slice(0,25)).then(function(r){
((r&&r.data)||[]).forEach(function(p){ window._cbarCache[p.id]=p; });
draw();
},draw);
} else draw();
};
/* ═══ ذاكرةُ الدردشة ═══
التحديث التفاضلي يحتاج ما لا يحمله الحدث: صورةَ صاحب الرسالة
ورتبتَه ومظهره، وقائمةَ رؤساء الكلانات. نحفظها عند أول تحميل
فتُرسم الرسائل التالية بلا سؤال الخادم. */
window._chOwners={}; window._chProf={};
/* ═══════════ تحميل الدردشة ═══════════
كانت بلا معالجةِ خطأٍ بتاتاً: أيّ فشلٍ في الاستعلام — رمزٌ انتهت
صلاحيته بعد رجوعٍ من الخلفية، أو انقطاعةُ شبكة — يجعل r.data
فارغاً، فيُمسح الصندوق ويُكتب «لا رسائل بعد». فيرى العضو دردشةً
خاوية ويظنّها ضاعت، ولا شيء يعيد المحاولة إلا إغلاق التطبيق.
الآن: الفشل لا يمسح شيئاً، ويُعاد مرّةً بعد تجديد الجلسة، وإن
بقي فارغاً قيل له ما جرى وأُعطي زرّاً. */
window._chatRetry=0;
window.loadChat=function(){
var box=$('chatMsgs'); if(!box) return; initChatPresence();
window.renderChatBar();
var hasRows=function(){ return !!box.querySelector('.cmsg'); };
var fail=function(why){
if(hasRows()) return; /* عنده رسائلُ معروضة — لا نُفزعه */
box.innerHTML='
';
};
var again=function(why){
/* محاولةٌ واحدة بعد تجديد الجلسة — أكثر من ذلك إلحاحٌ يُثقل */
if(window._chatRetry>=1) return fail(why);
window._chatRetry++;
sb.auth.refreshSession().then(function(){ window.loadChat(); },function(){ fail(why); });
};
sb.from('clans').select('created_by').then(function(cr){
if(cr&&cr.error) return again(cr.error.message);
var owners={}; (cr.data||[]).forEach(function(c){ if(c.created_by) owners[c.created_by]=1; });
window._chOwners=owners;
sb.from('messages').select('id,user_id,username,body,kind,media_url,created_at,reply_to,reply_name,reply_body,profiles(role,avatar_url,style_id)').order('id',{ascending:false}).limit(60).then(function(r){
if(r&&r.error) return again(r.error.message);
if(!r||!r.data) return again('لا بيانات');
window._chatRetry=0;
var rows=r.data.slice().reverse();
rows.forEach(function(m){ if(m.user_id && m.profiles) window._chProf[m.user_id]=m.profiles; });
box.innerHTML = rows.length? rows.map(function(m){ return chatMsgHTML(m,owners); }).join('') : '
لا رسائل بعد — كن أول من يكتب! 🎮
';
box.scrollTop=box.scrollHeight;
if(rows.length){ var maxId=0; rows.forEach(function(m){ if((m.id||0)>maxId) maxId=m.id; }); box._lastId=maxId; try{ localStorage.setItem('cx_chatSeen', String(maxId)); }catch(e){} }
window._chatUnread=0; updateChatBadge();
}, function(e){ again((e&&e.message)||'تعذّر الاتصال'); });
}, function(e){ again((e&&e.message)||'تعذّر الاتصال'); });
};
/* ═══ رسالةٌ واحدة تُلحَق — لا الدردشة كلها تُعاد ═══
كانت كل رسالةٍ واردة تُعيد جلب ستّين رسالةً وقائمةَ الكلانات ثم
تُعيد رسم الصندوق كاملاً. وفي دردشةٍ نشطة يعني ذلك عشرات
الاستعلامات في الدقيقة لكل متصل — وهو ما أثقل المنصة.
الحدث يحمل الرسالة كاملةً، فنُلحقها كما هي: صفرُ استعلام. */
window.chatAppend=function(m){
var box=$('chatMsgs'); if(!box || !m || !m.id) return;
if(box._lastId && m.id<=box._lastId) return; /* وصلت مرّتين */
var draw=function(){
box._lastId=m.id;
/* أول رسالةٍ تُزيح لافتة «لا رسائل بعد» */
if(/لا رسائل بعد/.test(box.textContent||'')) box.innerHTML='';
/* هل هو عند الأسفل؟ إن كان يقرأ القديم فلا نجرّه */
var atEnd = (box.scrollHeight - box.scrollTop - box.clientHeight) < 90;
box.insertAdjacentHTML('beforeend', chatMsgHTML(m, window._chOwners||{}));
while(box.children.length>80) box.removeChild(box.firstChild);
if(atEnd) box.scrollTop=box.scrollHeight;
try{ localStorage.setItem('cx_chatSeen', String(m.id)); }catch(e){}
};
var p=m.user_id && window._chProf[m.user_id];
if(p || !m.user_id){ m.profiles=p||null; return draw(); }
/* أول مرّةٍ يكتب فيها هذا اللاعب: نجلب صفَّه وحده ثم نحفظه */
sb.from('profiles').select('role,avatar_url,style_id,username').eq('id',m.user_id).single()
.then(function(r){ if(r&&r.data) window._chProf[m.user_id]=r.data; m.profiles=(r&&r.data)||null; draw(); },
function(){ m.profiles=null; draw(); });
};
window.sendChat=function(){
if(window._chatOff){ toast('الدردشة موقوفة مؤقتاً'); return; } if(_blocked()) return;
var inp=$('chatInput'); if(!inp) return; var body=(inp.value||'').trim(); if(!body) return; if(body.length>500) body=body.slice(0,500);
window.authUser().then(function(res){ var user=res&&res.data&&res.data.user; if(!user){ toast('سجّل دخولك أول'); go('account'); return; }
sb.from('profiles').select('username').eq('id',user.id).single().then(function(pr){
/* ═══ رسالتُك تظهر من ردّ الإدراج لا بإعادة تحميل ═══
كانت كل رسالةٍ ترسلها تُعيد جلب الدردشة كاملةً. والإدراج
يُرجع الصفَّ نفسه إن طلبناه — فنُلحقه مباشرةً. وإن سبقنا
البثُّ الحيُّ إليه فحارس المعرّف يمنع تكراره. */
var row={user_id:user.id,username:(pr.data&&pr.data.username)||'لاعب',body:body};
/* المقتبَس يُدفع مع الرسالة — نسخةٌ تُكتب مرّةً وتُقرأ ألف مرّة
بلا استعلامٍ لكلّ رسمة */
var re=window._chRe;
if(re && re.id){ row.reply_to=re.id; row.reply_name=re.nm; row.reply_body=re.body; }
sb.from('messages').insert(row)
.select('id,user_id,username,body,kind,media_url,created_at,reply_to,reply_name,reply_body').single()
.then(function(r){
if(r&&r.error){ toast('تعذّر الإرسال'); return; }
inp.value=''; window.chatReplyCancel();
if(r&&r.data&&window.chatAppend) window.chatAppend(r.data);
else if(window.loadChat) window.loadChat();
},function(){ toast('تعذّر الإرسال'); });
});
});
};
(function(){ var b=document.getElementById('chatSend'); if(b) b.addEventListener('click',window.sendChat); })();
/* ═══════════ الردّ على رسالة ═══════════
ثلاثة أفعال بمفوَّضٍ واحد على صندوق الرسائل: ↩ يفتح الشريط،
والاقتباس يقفز لأصله، و✕ يُلغي. مفوَّضٌ واحدٌ لأن الرسائل تُلحق
واحدةً واحدة، ولو ربطنا لكلٍّ مستمعاً لتراكمت الثمانون. */
window._chRe=null;
window.chatReplyTo=function(id,nm,body){
window._chRe={id:+id, nm:nm||'', body:body||''};
var w=$('chatRe'); if(!w) return;
$('chatReN').textContent='ترد على '+(nm||'لاعب');
$('chatReB').textContent=body||'';
w.hidden=false;
var i=$('chatInput'); if(i) i.focus();
};
window.chatReplyCancel=function(){
window._chRe=null; var w=$('chatRe'); if(w) w.hidden=true;
};
(function(){
var x=$('chatReX'); if(x) x.addEventListener('click', window.chatReplyCancel);
var box=$('chatMsgs'); if(!box) return;
box.addEventListener('click', function(e){
var dl=e.target&&e.target.closest?e.target.closest('[data-delmsg]'):null;
if(dl){
var mid=dl.getAttribute('data-delmsg');
if(!confirm('تُحذف هذه الرسالة للجميع، ويُقيَّد الحذف باسمك في السجل.\n\nمتأكد؟')) return;
var why=prompt('السبب (يُقيَّد في السجل):','إساءة');
if(why===null) return;
dl.disabled=true;
return sb.rpc('mod_delete_message',{p_id:parseInt(mid,10),p_reason:why||null})
.then(function(r){
var d=r&&r.data;
if(r&&r.error){ dl.disabled=false; return toast('تعذّر: '+r.error.message); }
if(!d||!d.ok){ dl.disabled=false;
return toast(d&&d.why==='forbidden'?'🔒 تحتاج مفتاح إشراف الدردشة':'ما لقيت الرسالة'); }
var row=dl.closest('.cmsg'); if(row) row.remove();
toast('🗑️ حُذفت رسالة '+(d.username||''));
},function(){ dl.disabled=false; toast('تعذّر الاتصال'); });
}
var t=e.target&&e.target.closest?e.target.closest('[data-reply]'):null;
if(t) return window.chatReplyTo(t.getAttribute('data-reply'),
t.getAttribute('data-rn'), t.getAttribute('data-rb'));
var q=e.target&&e.target.closest?e.target.closest('[data-goto]'):null;
if(!q) return;
/* الأصل قد يكون خرج من الثمانين المعروضة — نقول ذلك ولا نصمت */
var row=box.querySelector('.cmsg[data-mid="'+q.getAttribute('data-goto')+'"]');
if(!row){ toast('الرسالة الأصلية قديمة — خرجت من المعروض'); return; }
row.scrollIntoView({block:'center',behavior:'smooth'});
row.classList.remove('cmsg--hl'); void row.offsetWidth; row.classList.add('cmsg--hl');
});
})();
window.renderRsProofs=function(){
var box=$('rsProofList'); var arr=window._rsProofs||[];
if(box) box.innerHTML=arr.map(function(u,i){ return ''; }).join('');
if($('rsFileBtn')) $('rsFileBtn').textContent='📸 أضف صورة إثبات ('+arr.length+'/8)';
};
/* رفع إثبات على خادمنا — يُستعمل للنتائج وللبلاغات */
/* ═══ الرفعُ بتقدّمٍ مرئيّ ═══
fetch لا يُخبرُ كم رُفِع، فكان صاحبُ المقطعِ يرى «جارٍ الرفع» دقيقةً
كاملةً على شبكةِ جوّالٍ ويحسبُ المنصةَ معلّقةً فيُعيدُ الكرّةَ — فيرفعُ
المقطعَ مرّتَين. XHR يُعطي النسبة، فالانتظارُ المعلومُ يُحتمَل.
والملفُّ يُرسَلُ كما هو (Blob): المتصفّحُ يقرؤه من القرصِ على دفعات،
بخلافِ arrayBuffer التي تنسخُه كاملاً في ذاكرةِ الجوّالِ قبلَ أن تبدأَ
الإرسالَ أصلاً — وهذا وحدَه يُسقِطُ التبويبَ على مقطعٍ كبير. */
/* ═══ لماذا كان الرفعُ «ما يرفع» ═══
سجلُّ الخادمِ اليوم: ٤٧ ردَّ 400 و١٥ ردَّ 408 على /api/upload،
كلُّها من سفاري، وخدمةُ الوسائطِ تُسجّلُ «aborted» في اللحظةِ نفسِها —
أي أنّ الجهازَ قطعَ الاتصالَ في منتصفِ الإرسال، لا أنّ الخادمَ رفض.
ويقعُ ذلك حين يقفلُ العضوُ الشاشةَ أو ينتقلُ لتطبيقٍ آخرَ أثناءَ رفعِ
مقطعٍ طويل، فيُجمّدُ iOS الصفحةَ ويسقطُ الطلب. فعلاجُه ثلاثةٌ معاً:
تقليلُ البايتاتِ حتى ينتهيَ قبلَ أن يملّ، وقفلُ يقظةٍ يمنعُ إطفاءَ
الشاشةِ ما دامَ الرفعُ جارياً، وإعادةُ محاولةٍ صامتةٍ إن انقطع. */
/* ═══ لا يُعادُ التحميلُ وشيءٌ في الطريق ═══
عاملُ الخدمةِ يُعيدُ تحميلَ الصفحةِ متى وجدَ بناءً أحدث — وهو حسنٌ كي
لا يبقى أحدٌ على نسخةٍ قديمة. لكنّه كان يفعلُها **والمقطعُ في منتصفِ
الرفع**، فيموتُ الطلبُ عند صفرِ بايت. ورأيتُ ذلك في سجلِّ الخادم:
خمسةُ انقطاعاتٍ في نفسِ الملّي ثانية — والشبكةُ لا تقطعُ خمسةً بهذه
الدقّة، إنّما تُقطَعُ جميعاً حين تُعادُ الصفحة.
فصارَ لكلِّ رفعٍ عدّادٌ يرفعُه، والتحديثُ ينتظرُ حتى يفرغَ. */
window.codarBusy = 0;
window.codarHold = function(on){
window.codarBusy = Math.max(0, (window.codarBusy||0) + (on ? 1 : -1));
if(!on && !window.codarBusy && window._pendingReload){
window._pendingReload = false;
try{ sessionStorage.removeItem('codarBooted'); }catch(e){}
setTimeout(function(){ location.reload(); }, 400);
}
};
var _wake=null, _wakeN=0;
window.upKeepAwake=function(on){
try{
if(on){
_wakeN++;
if(!_wake && navigator.wakeLock && navigator.wakeLock.request)
navigator.wakeLock.request('screen').then(function(w){ _wake=w; },function(){});
} else {
_wakeN=Math.max(0,_wakeN-1);
if(!_wakeN && _wake){ try{ _wake.release(); }catch(e){} _wake=null; }
}
}catch(e){}
};
/* ═══ «فشل» وحدَها لا تُصلِحُ شيئاً ═══
العضوُ يقرأُ «فشل» فيُعيدُ الكرّةَ ويفشلُ ثانيةً، ونحن لا نعرفُ أهو
انقطاعُ شبكتِه أم كِبَرُ ملفِّه أم انتهاءُ جلستِه. فلكلِّ علّةٍ اسمُها
الآن، ولكلِّ اسمٍ ما يُفعَل. */
window.upWhy=function(x){
if(!x) return 'تعذّر الاتصال';
var d=x.j||{};
if(d.error) return d.error;
if(x.status===0) return 'انقطع الاتصال أثناء الرفع — جرّب على شبكة أثبت، ولا تغلق الشاشة';
if(x.status===401) return 'انتهت جلستك — سجّل دخولك من جديد';
if(x.status===403) return 'ما لك صلاحية على هذا';
if(x.status===413) return 'الملف كبير — قصّ المقطع أو صغّر الصورة';
if(x.status===415) return 'الملف مو صورة ولا مقطعاً صالحاً';
if(x.status===429) return 'رفعات كثيرة بوقت قصير — انتظر دقيقة';
if(x.status>=500) return 'عطل مؤقت بالخادم — أعد المحاولة';
return 'تعذّر الرفع ('+x.status+')';
};
window.upXHR=function(headers, blob, onProg, _try){
return new Promise(function(res){
window.upKeepAwake(true); window.codarHold(true);
var n=_try||0;
var fin=function(r){
window.upKeepAwake(false); window.codarHold(false);
/* ═══ الانقطاعُ يُعاوَدُ ثلاثاً بتمهّل ═══
سجلُّ الخادمِ يُظهرُ انقطاعاتٍ عند **صفرِ بايت** — أي أنّ الطلبَ
ماتَ قبلَ أن يبدأ: شبكةٌ متذبذبةٌ أو نفقٌ يُعادُ فتحُه. ومحاولةٌ
واحدةٌ لا تكفي لعبورِ ذلك، وثلاثٌ بتمهّلٍ تكفي غالباً. */
if(!r.ok && r.status===0 && n<2){
return setTimeout(function(){
window.upXHR(headers, blob, onProg, n+1).then(res);
}, 700*(n+1));
}
res(r);
};
/* ═══════════════════════════════════════════════════════
المسارُ القديمُ صارَ خطّةً ثانيةً لا الخطّةَ الوحيدة
───────────────────────────────────────────────────────
لم يُغيَّرْ فيه حرف — لُفَّ في دالّةٍ تُنادى إن تعذّرَ الرفعُ
المباشر. فأسوأُ ما يقعُ لو تعطّلَ R2 أن نعودَ إلى ما كنّا
عليه أمس، لا أن ينكسرَ الرفعُ في المنصّةِ كلِّها. */
var legacy=function(){
var x=new XMLHttpRequest();
x.open('POST','/api/upload');
Object.keys(headers||{}).forEach(function(k){ try{ x.setRequestHeader(k,headers[k]); }catch(e){} });
if(onProg) x.upload.onprogress=function(e){ if(e.lengthComputable) onProg(e.loaded/e.total); };
x.onload=function(){ var j=null; try{ j=JSON.parse(x.responseText); }catch(e){}
fin({ok:x.status>=200&&x.status<300, status:x.status, j:j}); };
x.onerror=function(){ fin({ok:false,status:0,j:null}); };
x.onabort=function(){ fin({ok:false,status:0,j:null}); };
x.ontimeout=function(){ fin({ok:false,status:0,j:null}); };
x.timeout=300000;
x.send(blob);
};
/* ═══════════════════════════════════════════════════════════
الرفعُ المباشرُ إلى R2
───────────────────────────────────────────────────────────
خطوتان: خادمُنا يوقّعُ (طلبٌ صغيرٌ جزءٌ من الثانية)، ثمّ يذهبُ
الملفُّ إلى أقربِ حافّةِ كلاودفلير — لا يمرُّ بهلسنكي ولا بذاكرةِ
خادمِنا. والفحصُ كلُّه بقيَ عندنا في خطوةِ التوقيع.
وكلُّ فشلٍ — توقيعٌ أو رفعٌ أو شبكة — يسقطُ إلى legacy() صامتاً.
المستخدمُ لا يرى إلا نجاحاً أو فشلاً واحداً، لا مسارَين. */
if(!window.R2_UP) return legacy();
var _mime=String((blob&&blob.type)||'').toLowerCase();
var _ext=/webp/.test(_mime)?'webp':/png/.test(_mime)?'png':/gif/.test(_mime)?'gif'
:/mp4/.test(_mime)?'mp4':/quicktime|mov/.test(_mime)?'mov'
:/webm/.test(_mime)?'webm':/mpeg|mp3/.test(_mime)?'mp3'
:/m4a|aac|mp4a/.test(_mime)?'m4a':'jpg';
var _h=headers||{};
var _kind=_h['x-codar-kind']||'avatar';
/* الصورةُ الشخصيةُ ولوقو الكلانِ يبقيانِ على المسارِ القديم:
الخادمُ يكتبُ رابطَهما في القاعدةِ بنفسِه بعدَ الرفع، وذلك
لا يقعُ في الرفعِ المباشر. */
if(_kind==='avatar'||_kind==='clan') return legacy();
fetch('/api/sign',{method:'POST',
headers:{'content-type':'application/json',
'Authorization':_h['Authorization']||'',
'x-codar-kind':_kind},
body:JSON.stringify({ext:_ext,size:(blob&&blob.size)||0,
ref:String(_h['x-codar-ref']||'').slice(0,64)})
}).then(function(r){ return r.ok?r.json():null; })
.then(function(j){
if(!j||!j.ok||!j.put) throw 0;
return new Promise(function(ok,bad){
var p=new XMLHttpRequest();
p.open('PUT', j.put);
if(onProg) p.upload.onprogress=function(e){ if(e.lengthComputable) onProg(e.loaded/e.total); };
p.onload=function(){ (p.status>=200&&p.status<300)?ok(j):bad(0); };
p.onerror=p.onabort=p.ontimeout=function(){ bad(0); };
p.timeout=300000;
p.send(blob);
});
})
.then(function(j){
window.upKeepAwake(false); window.codarHold(false);
res({ok:true,status:200,j:{ok:true,url:j.url,
kind:/mp4|mov|webm/.test(_ext)?'video':/m4a|mp3/.test(_ext)?'audio':'image'}});
})
.catch(function(){ legacy(); });
});
};
window.upProof=function(file, ref, onPct){
/* رؤوس HTTP لا تقبل إلا اللاتينية — نُنظّف المرجع دائماً */
ref = String(ref||'').replace(/[^0-9a-zA-Z-]/g,'') || 'general';
return window.shrinkImg(file).then(function(blob){
return sb.auth.getSession().then(function(sr){
var tok=sr&&sr.data&&sr.data.session&&sr.data.session.access_token;
if(!tok) return {ok:false, why:'سجّل دخولك'};
return window.upXHR({'Authorization':'Bearer '+tok,'x-codar-kind':'proof','x-codar-ref':ref},
blob, onPct)
.then(function(x){
if(!x.ok || !x.j || !x.j.url) return {ok:false, why:window.upWhy(x)};
return {ok:true, url:x.j.url};
});
});
}).catch(function(e){ return {ok:false, why:(e&&e.message)||'تعذّر الاتصال'}; });
};
/* ═══ الضغطُ قبلَ الرفع ═══
البايتُ الذي لا يُرسَلُ لا يُنتظَر. وWebP يُخرِجُ الصورةَ نفسَها في
نحوِ ثلثِ حجمِ JPEG عندَ جودةٍ متساوية، وخدمةُ الوسائطِ تعرفُ بصمتَه
أصلاً. ومن لا يدعمُه متصفّحُه (سفاري قديم) يردُّ الكانفاسُ PNG —
وهو أكبرُ لا أصغر — فنكتشفُ ذلك من نوعِ الناتجِ ونرجعُ إلى JPEG.
ولا نُصغّرُ إلى ما يُفقِدُ الدليلَ قيمتَه: ١٤٠٠ بكسلٍ تكفي لقراءةِ
لوحةِ النتائجِ وأسماءِ اللاعبين. */
window.shrinkImg=function(file){
return new Promise(function(res){
/* المقاطع تُرفع كما هي — الضغط للصور فقط */
if(!file || !/^image\//.test(file.type||'')) return res(file);
var img=new Image(), u=URL.createObjectURL(file);
img.onload=function(){
var mx=1400, w=img.width, h=img.height;
if(w>mx||h>mx){ if(w>h){ h=Math.round(h*mx/w); w=mx; } else { w=Math.round(w*mx/h); h=mx; } }
var cv=document.createElement('canvas'); cv.width=w; cv.height=h;
cv.getContext('2d').drawImage(img,0,0,w,h);
var done=function(b){ URL.revokeObjectURL(u); res(b&&b.size?b:file); };
try{
cv.toBlob(function(b){
if(b && b.type==='image/webp' && b.size) return done(b);
cv.toBlob(function(j){ done(j); },'image/jpeg',0.82);
},'image/webp',0.78);
}catch(e){ cv.toBlob(function(j){ done(j); },'image/jpeg',0.82); }
};
img.onerror=function(){ URL.revokeObjectURL(u); res(file); };
img.src=u;
});
};
window.uploadProof=function(){
var fi=$('rsFile'); if(!fi||!fi.files||!fi.files[0]) return; var f=fi.files[0];
var msg=$('rsUpMsg'); if(!msg) return;
window._rsProofs=window._rsProofs||[];
if(window._rsProofs.length>=8){ msg.style.color='var(--gold)'; msg.textContent='وصلت الحد الأقصى 8 صور'; fi.value=''; return; }
if(f.size>(window.UP_MAX_MB||100)*1024*1024){ msg.style.color='var(--red)'; msg.textContent='⚠️ الحجم أكبر من 15MB'; fi.value=''; return; }
msg.style.color='var(--tx3)'; msg.textContent='⏳ جاري رفع الإثبات...';
var reason='نتيجة التحدي';
var chId=$('rsChallenge')?$('rsChallenge').value:''; var ch=(window._myChallenges||{})[chId], cm=window._clanMap||{}, ta='', tb='';
if(ch){ ta=String((cm[ch.challenger_clan]||{}).tag||'؟'); tb=String((cm[ch.opponent_clan]||{}).tag||'؟'); }
var sc=(($('rsScore')&&$('rsScore').value)||'').trim();
/* الرفع على خادمنا مباشرة — الدالة القديمة لم تعد موجودة */
window.upProof(f, chId||'result').then(function(u){
if(!u.ok){ msg.style.color='var(--red)'; msg.textContent='تعذّر الرفع: '+u.why; return; }
window._rsProofs.push(u.url);
if($('rsProof')) $('rsProof').value=window._rsProofs.join(',');
window.renderRsProofs&&window.renderRsProofs();
msg.style.color='#3ddc84'; msg.textContent='✅ أُضيفت صورة ('+window._rsProofs.length+'/8)'; fi.value='';
});
};
(function(){ var fb=document.getElementById('rsFileBtn'), fi=document.getElementById('rsFile'); if(fb&&fi){ fb.addEventListener('click',function(){ fi.click(); }); fi.addEventListener('change',window.uploadProof); } })();
/* ═══ رمز البثّ ═══
سياسات RLS تُقيَّم على رمز المقبس لا على رمز الاستعلامات. فلو انضمّت
القناة برمز الزائر، ما وصلك صفٌّ يخصّك (تنبيهاتك · رسائلك · دعواتك)
ولو كنت مسجّلاً — وهذا سبب «ما يتحدّث إلا لو تنقّلت». */
window.rtAuth=function(tok){
/* setAuth صارت غير متزامنة في الإصدارات الحديثة — فمن بنى القناة
بعدها مباشرةً انضمّ برمز الزائر، فسقطت كل سياسةٍ تسأل عن الهوية
بصمت. ننتظرها دائماً قبل أي اشتراك. */
var apply=function(t){
if(!t) return Promise.resolve();
var r; try{ r=sb.realtime.setAuth(t); }catch(e){}
window._rtTok=t;
return (r && typeof r.then==='function') ? r.then(function(){return t;},function(){return t;})
: Promise.resolve(t);
};
try{
if(tok) return apply(tok);
return sb.auth.getSession().then(function(r){
var t=r&&r.data&&r.data.session&&r.data.session.access_token;
return t ? apply(t) : undefined;
},function(){});
}catch(e){ return Promise.resolve(); }
};
/* أي تجديد للرمز يُمرَّر للمقبس فوراً — بلا إعادة اشتراك */
try{
sb.auth.onAuthStateChange(function(ev, sess){
if(window.codarBootStep && !window._bootIdent && !(sess && sess.user)) window.codarBootStep('auth');
if(sess && sess.access_token) window.rtAuth(sess.access_token);
/* سجّل دخوله الآن: نفتح له الاتصال الحيّ (أو نعيد بناءه إن كان زائراً) */
/* ═══ متى تُبنى القناة؟ ═══
كان الشرط SIGNED_IN وحده — وهو لا يقع عند فتح صفحةٍ بجلسةٍ
محفوظة؛ هناك يقع INITIAL_SESSION. فتُبنى القناة في الإقلاع قبل
أن تُستعاد الجلسة، فتنضمّ بهوية زائر وتبقى كذلك طول الجلسة —
فلا يصل شيءٌ من الجداول التي تسأل عن الهوية. */
if((ev==='SIGNED_IN'||ev==='INITIAL_SESSION'||ev==='TOKEN_REFRESHED')
&& sess && sess.access_token && !window._rtRebuilt){
window._rtRebuilt=true;
window._rtGuest=false;
setTimeout(function(){
try{ window.rtAuth(sess.access_token).then(function(){
if(window.buildRt) window.buildRt(); },function(){ if(window.buildRt) window.buildRt(); });
}catch(e){ try{ if(window.buildRt) window.buildRt(); }catch(_){} }
}, 250);
}
if(ev==='SIGNED_OUT'){
try{ ((sb.getChannels&&sb.getChannels())||[]).forEach(function(c){ sb.removeChannel(c); }); }catch(e){}
window._rtRebuilt=false; window._rtTok=null;
}
});
}catch(e){}
window.buildRt=function(){
/* ═══ هويّةُ المشترِك ═══
تُلتقَطُ مرّةً هنا لأنَّ المرشِّحَ يُبنى نصّاً عندَ الاشتراك.
وbuildRt لا تُستدعى إلّا بعدَ ثبوتِ الجلسة، فالهويّةُ حاضرة.
وإن غابت (حالةُ زائر) نشترِكُ بلا مرشِّحٍ كما كان — الشرطُ في
المعالجِ يحمي، ولا نُسقِطُ إشعاراً بحجّةِ التحسين. */
var _rtMe = window._uid || null;
var _fNotif = _rtMe ? {filter:'user_id=eq.'+_rtMe} : {};
var _fDms = _rtMe ? {filter:'to_id=eq.'+_rtMe} : {};
/* قناة واحدة فقط — نُزيل القديمة قبل بناء الجديدة كي لا تتكاثر */
try{
((sb.getChannels&&sb.getChannels())||[]).forEach(function(c){
if(c.topic==='realtime:rt-all'){ try{ sb.removeChannel(c); }catch(e){} }
});
}catch(e){}
try{
sb.channel('rt-all')
.on('postgres_changes',{event:'*',schema:'public',table:'challenges'},function(payload){
if(payload.eventType==='INSERT'){
var ch=payload.new||{};
var mine = window._myClanId && ch.challenger_clan===window._myClanId;
var toMe = window._myClanId && ch.opponent_clan===window._myClanId;
if(!mine){
// إشعار النظام يرسله السيرفر (Web Push) — هنا تنبيه داخلي فقط لمنع التكرار
var title = toMe ? '📥 تحدي موجّه لكلانك!' : (ch.opponent_clan ? '' : '🎯 تحدي مفتوح جديد');
if(title) toast(title);
// عدّاد التحديات — يزيد إلا لو الشاشة مفتوحة قدامك
var cs=document.getElementById('challenges');
if(!(cs && cs.classList.contains('active'))){
window._chUnread=(window._chUnread||0)+1;
if(window.updateChBadge) window.updateChBadge();
} else if(window.clearChBadge) window.clearChBadge();
}
}
/* ═══ تغيّرت حالة تحدٍّ يخصّك ═══
القبول والرفض والانسحاب تصلك في اللحظة نفسها — بلا إعادة فتح.
ونتجاوز المهدّئ هنا لأن هذي لحظة ينتظرها اللاعب. */
if(payload.eventType==='UPDATE'){
var nw=payload.new||{}, od=payload.old||{};
var myIds=window._myIds||[];
var isMine = (window._myClanId && (nw.challenger_clan===window._myClanId || nw.opponent_clan===window._myClanId))
|| (myIds.indexOf(nw.challenger_clan)!==-1 || myIds.indexOf(nw.opponent_clan)!==-1);
if(isMine && od.status && nw.status && od.status!==nw.status){
var iSent = (nw.challenger_clan===window._myClanId) || (myIds.indexOf(nw.challenger_clan)!==-1);
var T={ accepted: iSent ? '✅ قُبل تحدّيكم — جهّزوا!' : '✅ قبلتم التحدي — بالتوفيق',
live: '⚡ بدأت المباراة',
completed:'🏁 انتهت المباراة — وثّقوا النتيجة',
cancelled:iSent ? '↩️ أُلغي طلبكم' : '↩️ أُلغي الطلب',
expired: '⏳ انتهت مدة الطلب' };
if(T[nw.status]) toast(T[nw.status]);
/* انتهى التحدي وأنت في غرفته: تُقفل أمامك فوراً */
if(window._cc && window._cc.id===nw.id && window.ccRoomState) window.ccRoomState(nw.status);
/* ═══ لحظة القبول ═══ أهمّ لحظة في المنصة تستحق أكثر من سطر.
نحن داخل isMine أصلاً — فالطرفان كلاهما يستحقها. والتاقات
تُجلب إن لم تكن بالخريطة (من كان في الرئيسية لا يملكها).
وكلها في try كي لا يقطع عطلٌ فيها التحديثَ الفوري بعدها. */
if(nw.status==='accepted' && fxOnce('clash', nw.id)){
try{
var _mineId=(myIds.indexOf(nw.challenger_clan)!==-1 || nw.challenger_clan===window._myClanId)
? nw.challenger_clan : nw.opponent_clan;
var _oppId=(_mineId===nw.challenger_clan)?nw.opponent_clan:nw.challenger_clan;
var _sub=(nw.playstyle==='rush'?'🔫 رشاش':'🔭 سنايبر')+' · أفضل من '+(nw.best_of||6);
var _m=window._clanMap||{};
var _tA=(_m[_mineId]||{}).tag, _tB=(_m[_oppId]||{}).tag;
if(_tA && _tB) window.fxClash(_tA, _tB, _sub, nw.id);
else sb.from('clans').select('id,tag').in('id',[_mineId,_oppId].filter(Boolean)).then(function(rq){
var mm={}; ((rq&&rq.data)||[]).forEach(function(c){ mm[c.id]=c.tag; });
window.fxClash(mm[_mineId], mm[_oppId], _sub, nw.id);
},function(){});
}catch(e){}
}
/* تحديث فوري بلا انتظار المهدّئ */
try{ _refreshActiveNow(); }catch(e){}
try{ if(window.chCountMine) window.chCountMine(); if(window.chCountNeed) window.chCountNeed(); if(window.chCountLive) window.chCountLive(); }catch(e){}
/* والبطاقة المفتوحة تتحدّث هي أيضاً */
try{ if(window._cc && window._cc.id===nw.id && window.openChChat) window.openChChat(nw.id); }catch(e){}
}
}
refreshActive('challenges');
})
.on('postgres_changes',{event:'UPDATE',schema:'public',table:'clans'},function(payload){
refreshActive('clans');
try{
var ov=payload.old||{}, nv=payload.new||{};
if(nv.id && nv.id===window._myClanId && typeof ov.points==='number' && typeof nv.points==='number' && ov.points!==nv.points && nv.points>ov.points){
if(window.fxRankProgress) window.fxRankProgress(ov.points, nv.points);
}
}catch(e){}
})
.on('postgres_changes',{event:'*',schema:'public',table:'clan_members'},function(){ refreshActive('clan_members'); })
/* ═══ إكمال الحياة ═══
هذي الجداول كانت تحتاج إعادة فتح الصفحة كي تظهر. أضفناها على
القناة نفسها — لا مقبس جديد ولا اشتراك جديد، والتكلفة صفر تقريباً.
وكلها تمرّ على المهدّئ نفسه: تحديث واحد كل ثانية ونصف مهما تدفّقت. */
.on('postgres_changes',{event:'*',schema:'public',table:'challenge_bids'},function(){ refreshActive('challenge_bids'); })
.on('postgres_changes',{event:'*',schema:'public',table:'challenge_results'},function(){ refreshActive('challenge_results'); })
.on('postgres_changes',{event:'*',schema:'public',table:'duel_results'},function(){ refreshActive('duel_results'); })
.on('postgres_changes',{event:'*',schema:'public',table:'clan_invites'},function(){
refreshActive('clan_invites'); if(window.loadInvites) window.loadInvites();
})
.on('postgres_changes',{event:'*',schema:'public',table:'clan_offers'},function(){
refreshActive('clan_offers'); if(window.loadOffers) window.loadOffers();
})
/* ═══ كل أحداث المواجهات لا إضافتها فقط ═══
القبول والإلغاء وانتهاء المهلة تغييراتٌ لا إضافات — فكانت لا
تصل إلا بتحديث الصفحة، بخلاف تحديات الكلانات المشتركة بـ'*'. */
.on('postgres_changes',{event:'*',schema:'public',table:'duels'},function(payload){
var d=payload.new||payload.old||{};
/* تغيّرت حالة مواجهة تخصّني؟ نعيد رسم الشاشة بلا ضجيج */
if(payload.eventType!=='INSERT'){
var sc0=document.getElementById('solo');
if(sc0&&sc0.classList.contains('active')&&window.loadSolo) window.loadSolo();
if(window._dqId && String(window._dqId)===String(d.id)){
window._dqRow=d; if(window.renderDuelRoom) window.renderDuelRoom();
}
/* ═══ لحظة القبول — نفس تأثير اصطدام الفرق، لكن لاعبين ═══ */
if(d.status==='accepted' && window._uid
&& (d.challenger_id===window._uid || d.opponent_id===window._uid)
&& typeof fxOnce==='function' && fxOnce('clash','duel'+d.id)){
try{
var _mineD=(d.challenger_id===window._uid)?d.challenger_name:d.opponent_name;
var _oppD =(d.challenger_id===window._uid)?d.opponent_name:d.challenger_name;
window.fxClash(_mineD, _oppD, '🎯 أول من يوصل '+(d.target_kills||30)+' كيل يفوز', null, true, function(){ window.openDuelRoomById(d.id); });
}catch(e){}
}
return;
}
if(!window._uid || d.challenger_id===window._uid) return;
var mine = d.opponent_id===window._uid;
if(mine) toast('🎯 '+(d.challenger_name||'لاعب')+' تحدّاك 1v1!');
else if(!d.opponent_id) toast('🎯 تحدي فردي مفتوح جديد');
var sc=document.getElementById('solo');
if(sc&&sc.classList.contains('active')){ if(window.loadSolo) window.loadSolo(); if(window.clearChBadge) window.clearChBadge(); }
else if(mine||!d.opponent_id){ window._chUnread=(window._chUnread||0)+1; if(window.updateChBadge) window.updateChBadge(); }
})
.on('postgres_changes',Object.assign({event:'INSERT',schema:'public',table:'dms'},_fDms),function(payload){
var m=payload.new||{};
if(m.to_id!==window._uid) return;
var d=document.getElementById('dm');
if(d&&d.classList.contains('active')&&window._dmWith===m.from_id){ if(window.loadDM) window.loadDM(); }
else { toast('💬 رسالة جديدة'); if(window.updateDMBadge) window.updateDMBadge(); var ib=document.getElementById('inbox'); if(ib&&ib.classList.contains('active')&&window.loadInbox) window.loadInbox(); }
})
/* الرسالة تُلحَق كما وصلت — لا إعادةَ تحميلٍ للدردشة كلها */
.on('postgres_changes',{event:'INSERT',schema:'public',table:'messages'},function(payload){
var m=payload.new||{}; var c=document.getElementById('chat');
if(c&&c.classList.contains('active')){ if(window.chatAppend) window.chatAppend(m); }
else if(m.user_id!==window._uid){ window._chatUnread=(window._chatUnread||0)+1; updateChatBadge(); }
})
.on('postgres_changes',{event:'INSERT',schema:'public',table:'match_log'},function(payload){
/* ═══ إعلان النتيجة ═══
تُعرض مرة واحدة لكل مباراة، ولمن كان طرفاً فيها وحده. */
try{
var m=payload.new||{};
var ids=window._myIds||[];
var mineId = (ids.indexOf(m.clan_a)!==-1) ? m.clan_a : (ids.indexOf(m.clan_b)!==-1 ? m.clan_b : null);
if(!mineId || !fxOnce('result', m.id||m.challenge_id)) return;
var isA=(mineId===m.clan_a);
window.fxResult({
mine: isA?m.tag_a:m.tag_b,
opp: isA?m.tag_b:m.tag_a,
myScore: isA?m.score_a:m.score_b,
oppScore: isA?m.score_b:m.score_a,
won: (m.winner_clan===mineId),
pts: (m.winner_clan===mineId)?m.delta_win:m.delta_loss
});
}catch(e){}
})
.on('postgres_changes',{event:'*',schema:'public',table:'transfers'},function(payload){
if(payload.eventType==='INSERT' && window._isStaff){ toast('🔄 طلب نقل جديد — راجعه بالإدارة'); }
var tf=document.getElementById('transfers'); if(tf&&tf.classList.contains('active')&&window.loadTransfers) window.loadTransfers();
var ad=document.getElementById('admin'); if(ad&&ad.classList.contains('active')&&window.loadAdminTransfers) window.loadAdminTransfers();
})
.on('postgres_changes',Object.assign({event:'INSERT',schema:'public',table:'notifications'},_fNotif),function(payload){
var n=payload.new||{};
if(n.user_id && n.user_id===window._uid){
if(n.title && n.title.indexOf('غرفة صوتية مفتوحة')!==-1 && n.url && window.showIncomingCall){
var _urlParts=(n.url||'').split('|');
window.showIncomingCall(_urlParts[0], n.body||'', _urlParts[1]||'');
return;
}
toast('🔔 '+(n.title||'تنبيه جديد'));
if(window.updateBellBadge) window.updateBellBadge();
var nf=document.getElementById('notifs'); if(nf&&nf.classList.contains('active')&&window.loadNotifs) window.loadNotifs();
if(('Notification'in window)&&Notification.permission==='granted'){ navigator.serviceWorker.getRegistration().then(function(reg){ var o={body:(n.body||''),icon:'/icon-192.png',badge:'/icon-192.png',dir:'rtl',lang:'ar',tag:'ntf',data:{url:'https://cod-ar.com/',screen:'notifs'}}; if(reg&®.showNotification) reg.showNotification('🔔 '+(n.title||'COD·AR'),o); else new Notification('🔔 '+(n.title||'COD·AR'),o); }); }
}
})
.on('postgres_changes',{event:'*',schema:'public',table:'reports'},function(payload){
/* صدر حكم على تحدٍّ مفتوح أمامك — يظهر فوراً بصفحته */
var rp0=payload.new||{};
if(rp0.challenge_id && window._cc && window._cc.id===rp0.challenge_id && window.loadVerdicts)
window.loadVerdicts(rp0.challenge_id);
if(payload.eventType==='INSERT' && window._isStaff){
toast('🚩 بلاغ مخالفة جديد — راجعه باللوحة');
if(('Notification'in window)&&Notification.permission==='granted'){ navigator.serviceWorker.getRegistration().then(function(reg){ var o={body:'دخل بلاغ جديد — افتح لوحة الإشراف',icon:'/icon-192.png',badge:'/icon-192.png',dir:'rtl',lang:'ar',tag:'rep',data:{url:'https://cod-ar.com/',screen:'admin'}}; if(reg&®.showNotification) reg.showNotification('🚩 بلاغ جديد',o); else new Notification('🚩 بلاغ جديد',o); }); }
} else if(payload.eventType==='UPDATE'){ var rp=payload.new||{}; if(rp.status==='resolved' && rp.reporter_id===window._uid){ toast('✅ تم البتّ في بلاغك: '+(rp.resolution||'')); } }
})
.on('postgres_changes',{event:'*',schema:'public',table:'join_requests'},function(payload){
var j=payload.new||payload.old||{};
/* طلبي أنا تغيّر (قُبل/رُفض)؟ أُحدّث حالتي كي يعود الزرّ أو تُضبط المهلة */
if(j.user_id && j.user_id===window._uid && window.loadMyJoinReqs){
window.loadMyJoinReqs().then(function(){
var cl=document.getElementById('clan');
if(cl&&cl.classList.contains('active')&&window._viewClan&&window.loadClanDetail) window.loadClanDetail(window._viewClan);
var hr1=document.getElementById('hiring');
if(hr1&&hr1.classList.contains('active')&&window.loadHiring) window.loadHiring();
});
}
/* قُبل الطلب أو رُفض ⇒ تُحدَّث القوائم بلا تنبيهٍ جديد */
if(payload.eventType!=='INSERT'){
var mc0=document.getElementById('manageclan');
if(mc0&&mc0.classList.contains('active')&&window.loadManageClan) window.loadManageClan();
var hr0=document.getElementById('hiring');
if(hr0&&hr0.classList.contains('active')&&window.loadHiring) window.loadHiring();
return;
}
if(window._myClanId && j.clan_id===window._myClanId){
toast('📥 طلب انضمام جديد من '+(j.username||'لاعب'));
if(('Notification'in window)&&Notification.permission==='granted'){ navigator.serviceWorker.getRegistration().then(function(reg){ var o={body:'PSN: '+(j.psn_id||'')+' — راجع طلبات الانضمام',icon:'/icon-192.png',badge:'/icon-192.png',dir:'rtl',lang:'ar',tag:'jr',data:{url:'https://cod-ar.com/',screen:'manageclan'}}; if(reg&®.showNotification) reg.showNotification('📥 طلب انضمام لكلانك',o); else new Notification('📥 طلب انضمام لكلانك',o); }); }
var mc=document.getElementById('manageclan'); if(mc&&mc.classList.contains('active')&&window.loadManageClan) window.loadManageClan();
}
})
/* نسخة التطبيق: أي نشر جديد يصل كل الأجهزة في نفس اللحظة */
/* أي إعداد يتغيّر من اللوحة يصل الأجهزة المفتوحة فوراً — بلا إعادة فتح */
.on('postgres_changes',{event:'*',schema:'public',table:'settings'},function(p){
var k=(p&&p.new&&p.new.key)||'';
if(k==='app_build') return; /* له معالج خاص تحت */
if(window.getSettingsForce) window.getSettingsForce();
})
.on('postgres_changes',{event:'*',schema:'public',table:'settings',filter:'key=eq.app_build'},function(p){
var v=(p&&p.new&&p.new.value)||'';
if(!v || !window.CODAR_BUILD || v===window.CODAR_BUILD) return;
if(window.checkBuildNow) window.checkBuildNow();
})
/* استدعاء المشرف: يظهر ويختفي لحظياً لطرفَي الغرفة */
.on('postgres_changes',{event:'*',schema:'public',table:'chat_mod_requests'},function(payload){
var q=payload.new||payload.old||{};
if(window._cc&&window._cc.id&&q.challenge_id===window._cc.id){
if(window.renderModReq) window.renderModReq(window._cc.id);
if(window.loadChChat) window.loadChChat(true);
}
if(window._dqId && String(q.duel_id||'')===String(window._dqId) && window.loadDuelChat){
window.loadDuelChat();
}
})
.on('postgres_changes',{event:'INSERT',schema:'public',table:'announcements'},function(payload){
var a=payload.new||{};
toast('📢 '+(a.title||'إشعار'));
if(('Notification'in window)&&Notification.permission==='granted'){
navigator.serviceWorker.getRegistration().then(function(reg){
var o={body:(a.body||''),icon:'icon-192.png',badge:'icon-192.png',dir:'rtl',lang:'ar',tag:'ann'};
if(reg&®.showNotification) reg.showNotification('📢 '+(a.title||'COD·AR'),o); else new Notification('📢 '+(a.title||'COD·AR'),o);
});
}
})
.subscribe(function(st){
window._rtState = st;
if(st==='SUBSCRIBED'){
window._rtJoinedAs = window._rtTok ? 'user' : 'anon';
window._rtTries = 0;
window._rtAt = Date.now();
/* عدنا بعد انقطاع؟ نُلحق ما فات — الأحداث لا تُعاد */
if(window._rtWasDown){ window._rtWasDown=false; try{ _refreshActiveNow(); }catch(e){} }
} else if(st==='CHANNEL_ERROR' || st==='TIMED_OUT' || st==='CLOSED'){
window._rtWasDown = true;
if(window.rtRevive) window.rtRevive('حالة: '+st);
}
});
}catch(e){ console.log('realtime',e); }
};
/* ═══════════════ حارس الاتصال الحيّ ═══════════════
المقبس يموت بصمت: يُغلقه الجوال حين ينام، أو تقطعه الشبكة عند تبدّل
البرج، أو ينتهي الرمز بعد ساعة. ولا يُخطرك أحد — تبقى في الصفحة
تنتظر وصولاً لن يصل. فنراقبه: نُعيده عند أي عطل، ونُلحق ما فات
عند عودته، ونتحقّق منه كلما رجعت إلى التطبيق. */
window._rtTries = 0;
window.rtAlive = function(){
try{
var chs = (sb.getChannels && sb.getChannels()) || [];
for(var i=0;i0){ card.style.display='none'; window._pushOk=true; return; }
say('🔔 إشعارات التحديات','⚠️ جهازك غير مسجّل — اضغط «تفعيل» ليوصلك التنبيه','تفعيل');
if(window.subscribePush) window.subscribePush();
},function(){});
};
/* حارس أعطال: خطأ في جزء لا يُسقط التطبيق كله */
window.addEventListener('error', function(e){
try{ console.log('app-error', e && e.message); }catch(_){}
});
window.addEventListener('unhandledrejection', function(e){
try{ console.log('app-reject', e && e.reason && (e.reason.message||e.reason)); }catch(_){}
});
/* ═══════════ دورة حياة التطبيق المثبّت ═══════════
الفرق بين سفاري والتطبيق أن سفاري يُعاد تحميله كثيراً، أما التطبيق
المثبّت فصفحة واحدة تعيش أسابيع وتُجمَّد بينها. فيتقادم فيه ثلاثة
أشياء: رمز الدخول، والكود، والبيانات. نعالج الثلاثة عند كل عودة. */
(function(){
var last=Date.now(), busy=false;
/* 1) الجلسة — أهمها. رمز الدخول يعيش ساعة، والتجميد يوقف تجديده. */
function reviveSession(){
if(!window.sb || !sb.auth) return Promise.resolve();
try{ if(sb.auth.startAutoRefresh) sb.auth.startAutoRefresh(); }catch(e){}
return sb.auth.getSession().then(function(r){
var ss=r&&r.data&&r.data.session;
if(!ss){ if(window._uid){ window._uid=null; if(window.renderIdentity) window.renderIdentity(null); } return; }
var left=(ss.expires_at||0)*1000 - Date.now();
/* أقل من دقيقتين على الانتهاء = جدّده الآن لا عند أول فشل */
if(left < 120000 && sb.auth.refreshSession){
return sb.auth.refreshSession().then(function(rr){
if(rr&&rr.error){ return; }
if(!window._uid && window.reloadProfile) window.reloadProfile();
},function(){});
}
if(!window._uid && window.reloadProfile) window.reloadProfile();
},function(){});
}
/* 2) الكود — بصمة البناء من الخادم مباشرة، بلا وسيط كاش */
/* عدّاد محاولات التحديث — يعيش للجلسة فقط */
function bTries(v){
try{
if(v===undefined) return parseInt(sessionStorage.getItem('cx_bt')||'0',10)||0;
sessionStorage.setItem('cx_bt', String(v));
}catch(e){}
return v||0;
}
/* مسح قشرة الكاش يضمن أن إعادة التحميل تجيب النسخة الجديدة فعلاً */
function wipeShell(){
try{
if(!window.caches) return Promise.resolve();
return caches.keys().then(function(ks){
return Promise.all(ks.map(function(k){ return caches.delete(k); }));
}).catch(function(){});
}catch(e){ return Promise.resolve(); }
}
/* ═══ لا قطيعَ مندفع ═══
حين تُنشر نسخةٌ جديدة تكتشفها كل الأجهزة في اللحظة نفسها. لو
أعادت كلها التحميل معاً، أعادت الاشتراك بـrealtime معاً — فيقفز
معالج سوبابيس (هذا ما علّق المنصة). فنُبعثر إعادة التحميل التلقائية
على نافذةٍ عشوائية (٥–٩٥ ثانية) فتتوزّع الأحمال ولا ترتفع ذروة.
أمّا حين يضغط المستخدم «تحديث» بنفسه فالتحديث فوريّ — طلبٌ واحدٌ لا قطيع. */
function doUpdate(immediate){
if(window._reloading) return; window._reloading=true;
var go=function(){ toast('⬆️ يتم تحديث التطبيق...');
wipeShell().then(function(){ setTimeout(function(){ location.reload(); }, 300); }); };
if(immediate){ go(); return; }
/* تأخيرٌ مبعثر — ولا نُعيد التحميل والمستخدم يكتب أو في شاشةٍ حسّاسة */
var delay=5000+Math.floor(Math.random()*90000);
setTimeout(function(){
var a=document.activeElement;
var typing=a&&(a.tagName==='INPUT'||a.tagName==='TEXTAREA'||a.isContentEditable);
var scr=(document.querySelector('.screen.active')||{}).id;
if(typing||window._authBusy===true||scr==='account'||scr==='settings'||scr==='chat'){
/* مشغول الآن؟ نؤجّل قليلاً ثم نعيد المحاولة بلا إلحاح */
window._reloading=false; setTimeout(function(){ doUpdate(false); }, 30000); return;
}
go();
}, delay);
}
function checkBuild(userForced){
if(!window.CODAR_BUILD) return Promise.resolve();
return fetch('/sw.js?ts='+Date.now(), {cache:'no-store'})
.then(function(r){ return r.ok?r.text():''; })
.then(function(t){
var m=/CACHE\s*=\s*['"]([^'"]+)['"]/.exec(t||'');
var live=m&&m[1];
if(!live) return;
if(live===window.CODAR_BUILD){ if(bTries()) bTries(0); return; }
/* حارس الحلقة: لو حاولنا مرّتين ولم تتغيّر البصمة، فالمشكلة
ليست في المستخدم — نتوقّف ونخبره بدل تجميد التطبيق */
var n=bTries();
if(n>=2){
if(!window._loopWarned){
window._loopWarned=true;
toast('⚠️ تعذّر تحديث التطبيق — أغلقه وافتحه، وإن تكرّر أعد تثبيته');
}
return;
}
bTries(n+1);
/* ═══ لا نسحب البساط من تحت أحد ═══
كان الشرط «هل المؤشّر في حقل؟» وحده. ولحظةَ يضغط المستخدم
«دخول» يخرج المؤشّر من الحقل — فيظنّ الحارس أنه فرغ ويُعيد
التحميل في منتصف تسجيل الدخول. فيبدو للناس أن المنصة
«معلّقة»: يكتبون، تُعاد الصفحة، يكتبون، تُعاد.
الآن نؤجّل ما دام في يده عمل: يكتب · أو في شاشة الحساب ·
أو دخولٌ جارٍ. والتحديث يُعرض عليه ولا يُفرض. */
var a=document.activeElement;
var typing=a&&(a.tagName==='INPUT'||a.tagName==='TEXTAREA'||a.isContentEditable);
var scr=(document.querySelector('.screen.active')||{}).id;
var busy = typing || window._authBusy===true ||
scr==='account' || scr==='settings' || scr==='newclan' || scr==='chat';
if(busy){
if(!window._pendingBuild){
window._pendingBuild=live;
/* شريطٌ لا يقاطع: يبقى حتى يضغطه صاحبه */
try{
var b=document.createElement('button');
b.id='updBar'; b.textContent='⬆️ نسخة جديدة جاهزة — اضغط للتحديث';
b.style.cssText='position:fixed;left:50%;transform:translateX(-50%);'+
'bottom:calc(86px + env(safe-area-inset-bottom));z-index:130;border:0;'+
'background:linear-gradient(90deg,#38b6ff,#1f7fd0);color:#04121f;font-family:inherit;'+
'font-weight:900;font-size:12.5px;padding:10px 16px;border-radius:22px;cursor:pointer;'+
'box-shadow:0 6px 20px rgba(0,0,0,.5)';
b.onclick=function(){ this.textContent='...جارٍ التحديث'; doUpdate(true); };
if(!document.getElementById('updBar')) document.body.appendChild(b);
}catch(e){ toast('⬆️ نسخة جديدة — أعد فتح التطبيق'); }
}
return;
}
/* ضغطها المستخدم بنفسه ⇒ فوريّ؛ وإلا مبعثرٌ كي لا يندفع القطيع */
doUpdate(!!userForced);
},function(){});
}
/* 3) البيانات — الاتصال الحي يموت بالتجميد بلا إشعار */
var _rvAt=0, _deadSince=0, _hardAt=0;
function reviveRealtime(){
try{
var now=Date.now();
if(now-_rvAt < 20000) return; /* لا نلحّ — الإلحاح هو الذي يهنّق */
_rvAt=now;
var ch=(sb.getChannels?sb.getChannels():[])||[];
var alive = ch.length>0 && ch.every(function(c){ return c.state==='joined'||c.state==='joining'; });
if(alive){ _deadSince=0; return; }
if(!_deadSince) _deadSince=now;
/* نوقظ الاتصال فقط — المكتبة تُعيد ضمّ قنواتها بنفسها.
(استدعاء subscribe() ثانيةً على نفس القناة يسبّب الدوّامة) */
if(sb.realtime && sb.realtime.connect) sb.realtime.connect();
/* بقي ميتاً دقيقتين؟ إعادة تشغيل واحدة نظيفة — لا أكثر */
if(now-_deadSince > 120000 && now-_hardAt > 300000){
_hardAt=now; _deadSince=now;
try{
if(sb.realtime && sb.realtime.disconnect) sb.realtime.disconnect();
setTimeout(function(){ try{ if(sb.realtime&&sb.realtime.connect) sb.realtime.connect(); }catch(e){} }, 800);
}catch(e){}
}
}catch(e){}
}
var _busyAt=0, _lastRefresh=0;
window.codarResume=function(force){
/* حارس: لو علقت عملية سابقة أكثر من 15ث نفكّها ولا نتجمّد */
if(busy && Date.now()-_busyAt < 15000) return;
busy=true; _busyAt=Date.now();
var gap=Date.now()-last; last=Date.now();
reviveSession().then(function(){
if(window.rtAuth) window.rtAuth();
reviveRealtime();
/* لا نعيد تحميل الشاشة مع كل تنقّل بين التطبيقات */
if(force || Date.now()-_lastRefresh > 30000){
_lastRefresh=Date.now();
try{ _refreshActiveNow(); }catch(e){}
}
if(window.updateBellBadge) window.updateBellBadge();
if(window.updateChBadge) window.updateChBadge();
if(window.updateDMBadge) window.updateDMBadge();
if(window.applyClanContext) window.applyClanContext();
if(window.checkPushReady) window.checkPushReady();
if(force || gap>20000) return checkBuild();
}).catch(function(){}).then(function(){ busy=false; },function(){ busy=false; });
};
var _sleepT=null;
document.addEventListener('visibilitychange', function(){
if(document.hidden){
try{ if(sb.auth&&sb.auth.stopAutoRefresh) sb.auth.stopAutoRefresh(); }catch(e){}
/* بعد 20ث بالخلفية نقطع الاتصال الحيّ — إبقاؤه يحاول وهو
مجمَّد هو ما يجمّد التطبيق عند العودة. التنقّل السريع بين
التطبيقات لا يقطعه بفضل المهلة. */
clearTimeout(_sleepT);
_sleepT=setTimeout(function(){
if(!document.hidden) return;
try{ if(sb.realtime && sb.realtime.disconnect) sb.realtime.disconnect(); }catch(e){}
}, 20000);
return;
}
clearTimeout(_sleepT); _sleepT=null;
try{ if(sb.realtime && sb.realtime.connect) sb.realtime.connect(); }catch(e){}
window.codarResume(true);
});
window.addEventListener('focus', function(){ if(Date.now()-last>10000) window.codarResume(false); });
window.addEventListener('online', function(){ window.codarResume(true); });
window.addEventListener('pageshow', function(e){ if(e.persisted) window.codarResume(true); });
/* نبض دوري: بيانات كل 20ث، وبصمة البناء كل 60ث */
/* الاتصال الحيّ يكفي حين يعمل — الاستطلاع احتياطٌ لموته لا بديل عنه */
function socketAlive(){
try{
var ch=(sb.getChannels?sb.getChannels():[])||[];
return ch.length>0 && ch.every(function(c){ return c.state==='joined'; });
}catch(e){ return false; }
}
setInterval(function(){
if(document.hidden) return;
reviveRealtime();
if(!socketAlive() && Date.now()-_lastRefresh > 30000){
_lastRefresh=Date.now();
try{ _refreshActiveNow(); }catch(e){}
}
}, 45000);
/* فحص البصمة كل 30ث — خفيف (ملف صغير) وصار آمناً بعد الحارس،
فالتحديث يصل شبه فوري كما في المنتدى */
setInterval(function(){ if(!document.hidden) checkBuild(); }, 30000);
setInterval(function(){ if(!document.hidden) reviveSession(); }, 120000);
/* نتيح استدعاءه من البثّ الحيّ */
window.checkBuildNow=checkBuild;
setTimeout(checkBuild, 4000);
})();
if(window.loadHome) window.loadHome();
if(window.initChatBadge) window.initChatBadge();
if(window.initChBadge) window.initChBadge();
if(window.applyUiTexts) window.applyUiTexts();
if(window.applyPerms) window.applyPerms();
/* بعد سحب التحديث نرجّعك لمكانك — ننتظر الجلسة كي تفتح الصفحات المحمية */
(function(){
try{ var q=new URLSearchParams(location.search);
if(q.get('s')||q.get('focus')) return; /* رابط مباشر أولى من المكان المحفوظ */
}catch(e){}
var n=0, t=setInterval(function(){
n++;
if(window._uid || window._authDone || n>20){ clearInterval(t); if(window._restoreRoute) window._restoreRoute(); }
}, 200);
})();
/* فتح بارد من إشعار أو رابط: /?s=challenges&focus= */
(function(){
try{
var q=new URLSearchParams(location.search);
var s=q.get('s'), f=q.get('focus');
/* يُخزَّنُ قبلَ `replaceState` أدناه — فهو يمحو المُعامِلاتِ بعدَ ٩٠٠ms */
var kd=q.get('kind'); if(kd) window._tkKind=kd;
if(!s && !f) return;
setTimeout(function(){
if(s==='player' && f && window.openPlayer) window.openPlayer(f);
else if(s==='chchat' && f && window.openChChat) window.openChChat(f);
/* غرفة مواجهة فردية — يحتاجها المشرف حين يُستدعى للفصل */
else if(s==='dq' && f && window.openDuelRoomById) window.openDuelRoomById(f);
else if(f && (!s || s==='challenges') && window._goChalOn) window._goChalOn(f);
else if(s) { go(s); if(f && window.focusChallenge) window.focusChallenge(f); }
try{ history.replaceState({}, '', location.pathname); }catch(e){}
}, 900);
}catch(e){}
})();
setTimeout(function(){ if(window.updateDMBadge) window.updateDMBadge(); if(window._uid&&window.loadFriends) window.loadFriends(); }, 2500);
try{ if(localStorage.getItem('cx_moved')==='1' && $('movedNote')) $('movedNote').hidden=false; }catch(e){}
})();
🏅 سلّم الرتب
منصة كلانات كول اوف ديوتي العربية
سجّل كلانك، اطلب تحدياً 6 ضد 6، وثّق النتيجة بالإثبات — وشاهد ترتيبك يتحدّث مباشرة في دوري الكلانات.
كود ار منصة تنافسية لكلانات كول اوف ديوتي: بلاك أوبس 1 في العالم العربي. تُسجَّل فيها الكلانات وتُطلَق التحديات 6 ضد 6، وتُوثَّق النتائج بالإثبات ويراجعها طاقم الإشراف قبل أن تدخل التصنيف — فلا ترتفع نقاط أحد بادّعاء ولا تنزل بشكوى بلا دليل.
وفيها الآن 154 كلاناً معتمداً و2609 لاعباً مسجّلاً و1796 نتيجة موثّقة و1 تحدياً مطروحاً الآن. وإلى جانب تحديات الكلانات هناك مواجهات فردية 1v1 لها تصنيفها المستقل، وأداة قياس الزوم التي تحسم الخلاف على الحرق بالرقم لا بالتقدير.