2026-03-11 07:15:45 +00:00

383 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ═══════════════════════════════════════════════════════════
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;