383 lines
19 KiB
JavaScript
383 lines
19 KiB
JavaScript
/* ═══════════════════════════════════════════════════════════
|
||
GAMEHUS — Core Engine
|
||
Auth Guard · Tab System · Wallet · Toast · Counters
|
||
═══════════════════════════════════════════════════════════ */
|
||
'use strict';
|
||
|
||
// ─── Fake Auth Store (will be replaced by Firebase Auth later) ───────────────
|
||
const TEST_USERS = [
|
||
{ email: 'admin@gamehus.com', password: 'admin123', username: 'Admin', role: 'admin', vip: true, gold: 15000, silver: 8400 },
|
||
{ email: 'vip@gamehus.com', password: 'vip123', username: 'VIP_Player', role: 'user', vip: true, gold: 5200, silver: 3100 },
|
||
{ email: 'test@gamehus.com', password: 'test123', username: 'TestUser', role: 'user', vip: false, gold: 0, silver: 1200 },
|
||
{ email: 'player1@gamehus.com', password: 'play123', username: 'ProShooter', role: 'user', vip: false, gold: 200, silver: 4500 },
|
||
{ email: 'player2@gamehus.com', password: 'play456', username: 'ChessMaster', role: 'user', vip: true, gold: 800, silver: 6700 },
|
||
];
|
||
|
||
const AUTH_KEY = 'gh_session';
|
||
const SESSION_TTL = 24 * 60 * 60 * 1000; // 24h
|
||
|
||
// ─── AUTH MODULE ─────────────────────────────────────────────────────────────
|
||
const Auth = (() => {
|
||
function getSession() {
|
||
try {
|
||
const raw = sessionStorage.getItem(AUTH_KEY) || localStorage.getItem(AUTH_KEY);
|
||
if (!raw) return null;
|
||
const data = JSON.parse(raw);
|
||
if (Date.now() - data.ts > SESSION_TTL) { clearSession(); return null; }
|
||
return data.user;
|
||
} catch { return null; }
|
||
}
|
||
|
||
function saveSession(user, remember = false) {
|
||
const payload = JSON.stringify({ user, ts: Date.now() });
|
||
sessionStorage.setItem(AUTH_KEY, payload);
|
||
if (remember) localStorage.setItem(AUTH_KEY, payload);
|
||
}
|
||
|
||
function clearSession() {
|
||
sessionStorage.removeItem(AUTH_KEY);
|
||
localStorage.removeItem(AUTH_KEY);
|
||
}
|
||
|
||
function login(identifier, password, remember = false) {
|
||
const user = TEST_USERS.find(u =>
|
||
(u.email === identifier || u.username === identifier) && u.password === password
|
||
);
|
||
if (!user) return { ok: false, msg: 'بيانات الدخول غير صحيحة' };
|
||
const { password: _, ...safeUser } = user;
|
||
saveSession(safeUser, remember);
|
||
return { ok: true, user: safeUser };
|
||
}
|
||
|
||
function logout() {
|
||
clearSession();
|
||
window.location.href = 'login.html';
|
||
}
|
||
|
||
function requireAuth() {
|
||
const user = getSession();
|
||
if (!user) {
|
||
window.location.replace('login.html');
|
||
return null;
|
||
}
|
||
// reveal page
|
||
document.querySelectorAll('.auth-guard').forEach(el => el.classList.add('revealed'));
|
||
return user;
|
||
}
|
||
|
||
return { getSession, saveSession, clearSession, login, logout, requireAuth };
|
||
})();
|
||
|
||
// ─── TOAST MODULE ─────────────────────────────────────────────────────────────
|
||
const Toast = (() => {
|
||
let container;
|
||
function getContainer() {
|
||
if (!container) {
|
||
container = document.createElement('div');
|
||
container.className = 'toast-container';
|
||
document.body.appendChild(container);
|
||
}
|
||
return container;
|
||
}
|
||
function show(msg, type = 'success', duration = 3000) {
|
||
const icons = { success: '✅', gold: '🪙', error: '❌', info: 'ℹ️' };
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast toast-${type}`;
|
||
toast.innerHTML = `<span>${icons[type] || '💬'}</span><span>${msg}</span>`;
|
||
getContainer().appendChild(toast);
|
||
setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateX(20px)'; toast.style.transition = 'all 0.3s'; setTimeout(() => toast.remove(), 300); }, duration);
|
||
}
|
||
return { show };
|
||
})();
|
||
|
||
// ─── TAB SYSTEM ───────────────────────────────────────────────────────────────
|
||
const TabSystem = (() => {
|
||
const instances = new Map(); // instanceId -> { tabs, activeId, container }
|
||
|
||
function create(containerId, tabs, options = {}) {
|
||
const container = document.getElementById(containerId);
|
||
if (!container) return;
|
||
|
||
const state = { tabs: [...tabs], activeId: tabs[0]?.id, container, options };
|
||
instances.set(containerId, state);
|
||
render(containerId);
|
||
return { activate: (id) => activate(containerId, id), addTab: (tab) => addTab(containerId, tab), removeTab: (id) => removeTab(containerId, id) };
|
||
}
|
||
|
||
function render(cid) {
|
||
const state = instances.get(cid);
|
||
if (!state) return;
|
||
const { tabs, activeId, container, options } = state;
|
||
|
||
// Tab bar
|
||
let barEl = container.querySelector('.tab-bar');
|
||
if (!barEl) { barEl = document.createElement('div'); barEl.className = 'tab-bar'; container.prepend(barEl); }
|
||
|
||
barEl.innerHTML = tabs.map(tab => `
|
||
<div class="tab-item ${tab.id === activeId ? 'active' : ''}" data-tid="${tab.id}">
|
||
${tab.icon ? `<span class="tab-icon">${tab.icon}</span>` : ''}
|
||
<span class="tab-label">${tab.label}</span>
|
||
${!tab.pinned ? `<button class="tab-close" data-close="${tab.id}">✕</button>` : ''}
|
||
</div>
|
||
`).join('') + (options.addable ? `<button class="tab-add" title="تبويب جديد">+</button>` : '');
|
||
|
||
// click events
|
||
barEl.querySelectorAll('.tab-item').forEach(el => {
|
||
el.addEventListener('click', (e) => {
|
||
if (e.target.closest('.tab-close')) return;
|
||
activate(cid, el.dataset.tid);
|
||
});
|
||
});
|
||
barEl.querySelectorAll('.tab-close').forEach(btn => {
|
||
btn.addEventListener('click', () => removeTab(cid, btn.dataset.close));
|
||
});
|
||
|
||
// panels
|
||
let panelsEl = container.querySelector('.tab-panels');
|
||
if (!panelsEl) { panelsEl = document.createElement('div'); panelsEl.className = 'tab-panels'; container.appendChild(panelsEl); }
|
||
|
||
tabs.forEach(tab => {
|
||
let panel = panelsEl.querySelector(`[data-panel="${tab.id}"]`);
|
||
if (!panel) {
|
||
panel = document.createElement('div');
|
||
panel.className = 'tab-panel animate-in';
|
||
panel.dataset.panel = tab.id;
|
||
panel.innerHTML = tab.content || '';
|
||
panelsEl.appendChild(panel);
|
||
}
|
||
panel.classList.toggle('active', tab.id === activeId);
|
||
});
|
||
}
|
||
|
||
function activate(cid, id) {
|
||
const state = instances.get(cid);
|
||
if (!state || !state.tabs.find(t => t.id === id)) return;
|
||
state.activeId = id;
|
||
render(cid);
|
||
state.options.onActivate?.(id);
|
||
}
|
||
|
||
function addTab(cid, tab) {
|
||
const state = instances.get(cid);
|
||
if (!state) return;
|
||
if (!state.tabs.find(t => t.id === tab.id)) state.tabs.push(tab);
|
||
activate(cid, tab.id);
|
||
}
|
||
|
||
function removeTab(cid, id) {
|
||
const state = instances.get(cid);
|
||
if (!state) return;
|
||
const idx = state.tabs.findIndex(t => t.id === id);
|
||
if (idx === -1 || state.tabs[idx].pinned) return;
|
||
state.tabs.splice(idx, 1);
|
||
if (state.activeId === id) state.activeId = state.tabs[Math.max(0, idx - 1)]?.id;
|
||
render(cid);
|
||
}
|
||
|
||
return { create, activate, addTab, removeTab };
|
||
})();
|
||
|
||
// ─── INNER TABS (simple, CSS-driven) ────────────────────────────────────────
|
||
function setupInnerTabs(containerEl) {
|
||
const tabs = containerEl.querySelectorAll('.inner-tab');
|
||
const panels = containerEl.querySelectorAll('.inner-panel');
|
||
tabs.forEach(tab => {
|
||
tab.addEventListener('click', () => {
|
||
tabs.forEach(t => t.classList.remove('active'));
|
||
panels.forEach(p => p.classList.remove('active'));
|
||
tab.classList.add('active');
|
||
const target = containerEl.querySelector(`[data-inner-panel="${tab.dataset.innerTab}"]`);
|
||
if (target) target.classList.add('active');
|
||
});
|
||
});
|
||
if (tabs[0]) tabs[0].click();
|
||
}
|
||
|
||
// ─── TOPBAR BUILDER ─────────────────────────────────────────────────────────
|
||
function buildTopbar(user) {
|
||
const bar = document.getElementById('topbar');
|
||
if (!bar || !user) return;
|
||
bar.innerHTML = `
|
||
<div class="topbar-brand">
|
||
<span class="gem">💎</span>
|
||
<span class="name gold-text">GAMEHUS</span>
|
||
</div>
|
||
<div class="wallet-strip">
|
||
<div class="wallet-coin wallet-gold" id="wallet-gold" title="ذهب - عملة مدفوعة">
|
||
🪙 <span id="gold-amount">${(user.gold||0).toLocaleString('ar')}</span>
|
||
</div>
|
||
<div class="wallet-coin wallet-silver" id="wallet-silver" title="فضة - عملة مجانية">
|
||
🔘 <span id="silver-amount">${(user.silver||0).toLocaleString('ar')}</span>
|
||
</div>
|
||
</div>
|
||
<div class="online-badge hide-mobile">
|
||
<span class="online-dot"></span>
|
||
<span id="online-count">8,432</span> متصل
|
||
</div>
|
||
<div class="topbar-user" id="user-menu-btn">
|
||
<div class="user-avatar">${(user.username||'?').substring(0,2).toUpperCase()}</div>
|
||
<div>
|
||
<div class="user-name">${user.username}</div>
|
||
${user.vip ? '<div class="user-vip">✨ VIP</div>' : ''}
|
||
</div>
|
||
<span style="color:var(--text-muted);font-size:0.75rem;">▾</span>
|
||
</div>
|
||
`;
|
||
|
||
// user menu dropdown
|
||
document.getElementById('user-menu-btn')?.addEventListener('click', () => {
|
||
const existing = document.getElementById('user-dropdown');
|
||
if (existing) { existing.remove(); return; }
|
||
const dropdown = document.createElement('div');
|
||
dropdown.id = 'user-dropdown';
|
||
dropdown.style.cssText = `position:fixed;top:${65}px;left:16px;background:var(--bg-card);border:1px solid var(--border-gold);border-radius:12px;padding:8px;min-width:180px;z-index:100;box-shadow:var(--glow-gold);animation:fadeIn 0.2s ease;`;
|
||
dropdown.innerHTML = `
|
||
<div style="padding:10px 12px 8px;border-bottom:1px solid var(--border-dim);margin-bottom:6px;">
|
||
<div style="font-weight:800;color:var(--text-main);">${user.username}</div>
|
||
<div style="font-size:0.75rem;color:var(--text-muted);">${user.email}</div>
|
||
</div>
|
||
<a href="profile.html" style="display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:8px;color:var(--text-muted);font-size:0.85rem;transition:all 0.2s;" onmouseover="this.style.background='rgba(255,255,255,0.05)'" onmouseout="this.style.background='transparent'">👤 الملف الشخصي</a>
|
||
<div style="display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:8px;color:var(--text-muted);font-size:0.85rem;cursor:pointer;transition:all 0.2s;" onmouseover="this.style.background='rgba(212,160,23,0.07)';this.style.color='var(--gold)'" onmouseout="this.style.background='transparent';this.style.color='var(--text-muted)'" onclick="openShop()">🪙 شحن الذهب</div>
|
||
<div style="height:1px;background:var(--border-dim);margin:6px 0;"></div>
|
||
<div style="display:flex;align-items:center;gap:8px;padding:9px 12px;border-radius:8px;color:#f87171;font-size:0.85rem;cursor:pointer;transition:all 0.2s;" onmouseover="this.style.background='rgba(239,68,68,0.08)'" onmouseout="this.style.background='transparent'" onclick="Auth.logout()">🚪 تسجيل الخروج</div>
|
||
`;
|
||
document.body.appendChild(dropdown);
|
||
setTimeout(() => document.addEventListener('click', function handler(e) {
|
||
if (!dropdown.contains(e.target) && e.target.id !== 'user-menu-btn') { dropdown.remove(); document.removeEventListener('click', handler); }
|
||
}), 10);
|
||
});
|
||
|
||
// pulse online count
|
||
const el = document.getElementById('online-count');
|
||
if (el) {
|
||
const base = 8432;
|
||
let tick = 0;
|
||
setInterval(() => { tick++; el.textContent = (base + Math.round(Math.sin(tick/3)*18)).toLocaleString('ar'); }, 7000);
|
||
}
|
||
}
|
||
|
||
// ─── SIDEBAR BUILDER ─────────────────────────────────────────────────────────
|
||
function buildSidebar(user, activePage = '') {
|
||
const sb = document.getElementById('sidebar');
|
||
if (!sb) return;
|
||
|
||
const items = [
|
||
{ id: 'dashboard', icon: '🏠', label: 'الرئيسية', href: 'index.html' },
|
||
{ id: 'games', icon: '🎮', label: 'الألعاب', href: 'games.html' },
|
||
{ id: 'rooms', icon: '🏠', label: 'الغرف', href: 'rooms.html' },
|
||
{ id: 'top', icon: '🏆', label: 'المتصدرون', href: 'top.html' },
|
||
{ id: 'chat', icon: '💬', label: 'الدردشة', href: 'chat.html', badge: '3' },
|
||
{ divider: true },
|
||
{ id: 'train', icon: '🚂', label: 'محطات الجوائز', href: 'train.html' },
|
||
{ id: 'shop', icon: '🛍️', label: 'المتجر', href: 'shop.html' },
|
||
{ id: 'tournaments',icon:'🥇', label: 'البطولات', href: 'tournaments.html' },
|
||
{ divider: true },
|
||
{ id: 'profile', icon: '👤', label: 'الملف الشخصي', href: 'profile.html' },
|
||
...(user?.role === 'admin' ? [{ id: 'admin', icon: '⚙️', label: 'لوحة الإدارة', href: 'admin.html' }] : []),
|
||
];
|
||
|
||
sb.innerHTML = `
|
||
<div class="sidebar-section">
|
||
<div class="sidebar-label">القائمة الرئيسية</div>
|
||
${items.map(item => {
|
||
if (item.divider) return `<div class="sidebar-divider"></div>`;
|
||
const active = item.id === activePage;
|
||
return `<a href="${item.href}" class="sidebar-item ${active ? 'active' : ''}">
|
||
<span class="icon">${item.icon}</span>
|
||
<span>${item.label}</span>
|
||
${item.badge ? `<span class="badge-count">${item.badge}</span>` : ''}
|
||
</a>`;
|
||
}).join('')}
|
||
</div>
|
||
<div style="padding:16px 12px;margin-top:auto;">
|
||
<div style="background:linear-gradient(135deg,rgba(212,160,23,0.12),rgba(122,92,12,0.08));border:1px solid var(--border-gold);border-radius:12px;padding:14px;">
|
||
${user?.vip ? `
|
||
<div class="vip-badge" style="margin-bottom:8px;">✨ VIP مفعّل</div>
|
||
<div style="font-size:0.75rem;color:var(--text-muted);">تذكرة القطار الذهبي نشطة</div>
|
||
` : `
|
||
<div style="font-size:0.82rem;font-weight:700;color:var(--gold);margin-bottom:6px;">🚂 ترقية إلى VIP</div>
|
||
<div style="font-size:0.73rem;color:var(--text-muted);margin-bottom:10px;">جوائز أكبر في كل المحطات</div>
|
||
<a href="shop.html" class="btn-gold btn btn-sm" style="width:100%;justify-content:center;display:flex;">اشترِ التذكرة</a>
|
||
`}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// ─── COUNTERS ────────────────────────────────────────────────────────────────
|
||
function animateCounters() {
|
||
document.querySelectorAll('[data-count]').forEach(el => {
|
||
const target = parseInt(el.getAttribute('data-count') || '0', 10);
|
||
if (!target) return;
|
||
let cur = 0; const inc = target / 60;
|
||
const t = setInterval(() => {
|
||
cur += inc;
|
||
if (cur >= target) { cur = target; clearInterval(t); }
|
||
el.textContent = Math.floor(cur).toLocaleString('ar');
|
||
}, 16);
|
||
});
|
||
}
|
||
|
||
// ─── SHOP MODAL (quick open) ──────────────────────────────────────────────────
|
||
function openShop() {
|
||
const existing = document.getElementById('shop-modal');
|
||
if (existing) existing.remove();
|
||
const modal = document.createElement('div');
|
||
modal.id = 'shop-modal';
|
||
modal.className = 'modal-overlay';
|
||
modal.innerHTML = `
|
||
<div class="modal-box" style="max-width:480px;">
|
||
<button onclick="document.getElementById('shop-modal').remove()" style="position:absolute;top:14px;left:14px;background:none;border:none;color:var(--text-muted);font-size:1.2rem;cursor:pointer;">✕</button>
|
||
<div style="text-align:center;margin-bottom:24px;">
|
||
<div style="font-size:2.5rem;margin-bottom:8px;">🪙</div>
|
||
<h2 class="gold-text" style="font-size:1.4rem;font-weight:900;">شحن الذهب</h2>
|
||
<p style="color:var(--text-muted);font-size:0.85rem;margin-top:4px;">الذهب عملة مدفوعة تُستخدم لشراء العناصر الحصرية</p>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:20px;">
|
||
${[
|
||
{ gold: 500, price: '4.99$', label: 'مبتدئ', bonus: '' },
|
||
{ gold: 1200, price: '9.99$', label: 'شائع', bonus: '+100 مجاناً' },
|
||
{ gold: 2800, price: '19.99$', label: 'محترف', bonus: '+300 مجاناً' },
|
||
{ gold: 6500, price: '39.99$', label: 'احترافي', bonus: '+700 مجاناً' },
|
||
].map(p => `
|
||
<div style="background:var(--bg-surface);border:1px solid var(--border-gold);border-radius:12px;padding:16px;text-align:center;cursor:pointer;transition:all 0.2s;" onmouseover="this.style.boxShadow='var(--glow-gold)'" onmouseout="this.style.boxShadow='none'" onclick="Toast.show('قريباً! سيتم ربط بوابة الدفع.','gold');document.getElementById('shop-modal').remove()">
|
||
<div style="font-size:1.5rem;margin-bottom:4px;">🪙</div>
|
||
<div style="font-size:1.1rem;font-weight:900;color:var(--gold);">${p.gold.toLocaleString()}</div>
|
||
<div style="font-size:0.7rem;color:var(--text-muted);">${p.label}</div>
|
||
${p.bonus ? `<div style="font-size:0.65rem;color:var(--green);margin-top:2px;">${p.bonus}</div>` : ''}
|
||
<div style="margin-top:10px;font-size:0.88rem;font-weight:800;color:var(--text-main);">${p.price}</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
<div style="background:rgba(212,160,23,0.06);border:1px solid rgba(212,160,23,0.15);border-radius:10px;padding:12px;font-size:0.78rem;color:var(--text-muted);text-align:center;">
|
||
🔒 جميع المعاملات آمنة ومشفرة — سيتم ربط بوابة الدفع قريباً
|
||
</div>
|
||
</div>
|
||
`;
|
||
modal.addEventListener('click', e => { if (e.target === modal) modal.remove(); });
|
||
document.body.appendChild(modal);
|
||
}
|
||
|
||
// ─── PAGE INIT ────────────────────────────────────────────────────────────────
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const page = document.body.dataset.page || '';
|
||
const isAuthPage = ['login', 'register'].includes(page);
|
||
|
||
if (!isAuthPage) {
|
||
const user = Auth.requireAuth();
|
||
if (!user) return;
|
||
buildTopbar(user);
|
||
buildSidebar(user, page);
|
||
animateCounters();
|
||
// setup any inner-tab containers on the page
|
||
document.querySelectorAll('[data-inner-tabs]').forEach(el => setupInnerTabs(el));
|
||
}
|
||
});
|
||
|
||
// expose globally
|
||
window.Auth = Auth;
|
||
window.Toast = Toast;
|
||
window.TabSystem = TabSystem;
|
||
window.openShop = openShop;
|
||
window.setupInnerTabs = setupInnerTabs;
|