/* Modal */
.modal{position:fixed;inset:0;background:rgba(6,7,15,.75);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;padding:20px}
.modal.open{display:flex}
.modal-card{width:min(960px,100%);background:var(--panel-2);border:1px solid #242863;border-radius:20px;box-shadow:var(--shadow);overflow:hidden}
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid #242863}
.modal-body{padding:16px}
.close{cursor:pointer;border:none;background:transparent;color:var(--text);font-size:20px}
All
Slots
Poker
Roulette
Blackjack
Live
Crash
Table
Current Status: Not Submitted
Start KYC
Reset
Production flow should collect legal name, DOB, address, and verify ID (Aadhaar/Passport/Driving Licence) + selfie liveness via a compliant provider. Store only minimal data, encrypt at rest, and purge per policy.
Daily Deposit Limit (₹)
Daily Loss Limit (₹)
Session Timer (min)
Save Limits
Self-Exclude (7 days)
Only admins can view. Use demo login (e.g., admin@admin.test) to get admin role locally. Replace with Firebase Auth + role claims in production.
You are not an admin. Please login as an admin user.
GameCategory
✕
Login✕
Continue
Demo auth stores a fake session in localStorage. Replace with Firebase Auth before production.
Firebase Setup (placeholder)
// 1) Add your Firebase config here
// const firebaseConfig = { apiKey:"...", authDomain:"...", projectId:"...", appId:"..." };
// 2) Initialize Firebase & Auth: initializeApp(firebaseConfig); getAuth();
// 3) Use signInWithEmailAndPassword / createUserWithEmailAndPassword
// 4) Store custom claims (admin) via backend Cloud Functions
${icon}
Demo
Play
`;
card.querySelector('[data-open]').onclick = ()=> openGame({name,cat});
grid.appendChild(card);
})
}
function mockLeader(){
const names = ['Arjun','Riya','Kabir','Aisha','Rohan','Meera','Vikram','Isha','Aarav','Sana'];
const picks = Array.from({length:8},()=>({
user:names[Math.floor(Math.random()*names.length)],
game: games[Math.floor(Math.random()*games.length)][2],
amt: Math.floor(Math.random()*5000)+500
}))
leader.innerHTML = '';
picks.forEach(p=>{
const row = document.createElement('div'); row.className='row';
row.innerHTML = `🏆 ${p.user} won ${formatINR(p.amt)}
${p.game}
`
leader.appendChild(row);
})
}
// Filters & Search
let currentCat='all';
document.querySelectorAll('.chip').forEach(ch=>{
ch.addEventListener('click',()=>{
document.querySelectorAll('.chip').forEach(c=>c.classList.remove('active'));
ch.classList.add('active'); currentCat = ch.dataset.cat;
renderGames(currentCat, search.value.trim().toLowerCase());
})
})
const search = document.getElementById('search');
search.addEventListener('input',()=> renderGames(currentCat, search.value.trim().toLowerCase()));
// ---------------- WALLET ----------------
function renderWallet(){
walletBalanceEl.textContent = formatINR(state.user.balance);
txTableBody.innerHTML = '';
state.tx.slice().reverse().forEach(t=>{
const tr=document.createElement('tr');
tr.innerHTML = `${t.id}${t.type}${formatINR(t.amt)}${t.status}${new Date(t.ts).toLocaleString()}`;
txTableBody.appendChild(tr);
})
}
// Add a mock transaction helper
function addTx(type, amt, status='completed'){
state.tx.push({id: 'TX'+Math.random().toString(36).slice(2,8).toUpperCase(), type, amt, status, ts: Date.now(), user: state.user.email||'guest'});
save(); renderWallet(); renderAdmin();
}
// ---------------- KYC & RG ----------------
const kycStatusEl = document.getElementById('kycStatus');
const rgMsg = document.getElementById('rgMsg');
function renderKycRg(){
// KYC
kycStatusEl.textContent = state.user.kyc==='approved'? 'Approved' : (state.user.kyc==='pending'? 'Pending' : 'Not Submitted');
kycStatusEl.className = 'flag ' + (state.user.kyc==='approved'?'good':(state.user.kyc==='pending'?'warn':'bad'));
// RG inputs
document.getElementById('rgLimitDeposit').value = state.user.rg.limitDeposit;
document.getElementById('rgLimitLoss').value = state.user.rg.limitLoss;
document.getElementById('rgTimer').value = state.user.rg.timer;
// self-exclusion message
const now = Date.now();
if(state.user.rg.selfExcludedUntil>now){
const days = Math.ceil((state.user.rg.selfExcludedUntil-now)/86400000);
rgMsg.textContent = `Self-excluded. ${days} day(s) remaining.`;
} else { rgMsg.textContent = ''; }
}
document.getElementById('btnStartKyc').onclick = ()=>{ state.user.kyc='pending'; save(); renderKycRg(); alert('KYC started (demo). In production, open provider flow.'); };
document.getElementById('btnResetKyc').onclick = ()=>{ state.user.kyc='none'; save(); renderKycRg(); };
document.getElementById('saveRG').onclick = ()=>{
const d = parseInt(document.getElementById('rgLimitDeposit').value||'0');
const l = parseInt(document.getElementById('rgLimitLoss').value||'0');
const t = parseInt(document.getElementById('rgTimer').value||'0');
state.user.rg.limitDeposit=d; state.user.rg.limitLoss=l; state.user.rg.timer=t; save();
rgMsg.textContent = 'Limits saved (demo).'; renderAdmin();
}
document.getElementById('selfExclude').onclick = ()=>{ state.user.rg.selfExcludedUntil = Date.now() + 7*86400000; save(); renderKycRg(); };
// ---------------- ADMIN ----------------
const adminGate = document.getElementById('adminGate');
const adminWrap = document.getElementById('adminWrap');
function renderAdmin(){
if(!(state.user && state.user.admin)){
adminGate.style.display='block'; adminWrap.style.display='none'; return;
}
adminGate.style.display='none'; adminWrap.style.display='block';
// Users
const uBody = document.querySelector('#adminUsers tbody'); uBody.innerHTML='';
const u = state.user;
const rgFlags = (u.rg.selfExcludedUntil>Date.now()? 'Self-excluded ' : '') +
`Dep ₹${u.rg.limitDeposit} Loss ₹${u.rg.limitLoss}`;
const trU=document.createElement('tr'); trU.innerHTML = `${u.email||'guest'}${u.kyc}${formatINR(u.balance)}${rgFlags}${u.admin?'admin':'user'}`; uBody.appendChild(trU);
// Games
const gBody = document.querySelector('#adminGames tbody'); gBody.innerHTML='';
games.slice(0,10).forEach(([cat,icon,name])=>{
const tr=document.createElement('tr'); tr.innerHTML = `${name}${cat}96.0active`; gBody.appendChild(tr);
})
// Transactions
const tBody = document.querySelector('#adminTx tbody'); tBody.innerHTML='';
state.tx.slice(-10).reverse().forEach(t=>{
const tr=document.createElement('tr'); tr.innerHTML = `${t.id}${t.user}${t.type}${formatINR(t.amt)}${t.status}`; tBody.appendChild(tr);
})
// Compliance alerts
const ca = document.getElementById('compAlerts'); ca.innerHTML='';
if(u.kyc!=='approved') addAlert('KYC not approved — restrict withdrawals.');
if(u.rg.selfExcludedUntil>Date.now()) addAlert('User self-excluded — block gameplay.');
function addAlert(msg){ const li=document.createElement('li'); li.textContent = '• ' + msg; ca.appendChild(li); }
}
// ---------------- GAME MODAL & PLAYABLE DEMOS (unchanged) ----------------
const modal = document.getElementById('modal');
const closeModal = document.getElementById('closeModal');
const gameTitle = document.getElementById('gameTitle');
const gameCat = document.getElementById('gameCat');
const gameArea = document.getElementById('gameArea');
const gameStatus = document.getElementById('gameStatus');
function openGame({name,cat}){
// RG check: self-exclusion
if(state.user.rg.selfExcludedUntil > Date.now()){
alert('You are self-excluded until your cooling period ends.'); return;
}
gameTitle.textContent = name; gameCat.textContent = cat; gameStatus.textContent=''; gameArea.innerHTML=''; modal.classList.add('open');
if(demoPlayable[cat] && demoPlayable[cat].includes(name)){
({Slots:slotsDemo, Roulette:rouletteDemo, Blackjack:blackjackDemo, Crash:crashDemo}[cat])();
}else{
const wrap = document.createElement('div');
wrap.innerHTML = `
This title is a placeholder. Build its real mechanics later. For now, try one of the playable demos:
🎰 Slots Demo
🎡 Roulette Demo
🃏 Blackjack Demo
✈️ Crash Demo
`
gameArea.appendChild(wrap);
}
}
closeModal.onclick = ()=> modal.classList.remove('open');
window.addEventListener('keydown',e=>{if(e.key==='Escape') modal.classList.remove('open')});
function updateBalance(){ balanceEl.textContent = formatINR(state.user.balance); walletBalanceEl.textContent = formatINR(state.user.balance); }
// Slots Demo
function slotsDemo(){
const symbols = ['🍒','🍋','🔔','⭐','💎','7️⃣'];
const el = document.createElement('div');
el.innerHTML = `
❔
❔
❔
Spin
Bet on
Red
Black
0
Spin Wheel
🎡
`;
gameArea.appendChild(el);
let selection = 'RED';
el.querySelectorAll('[data-bet]').forEach(b=> b.onclick = ()=> selection=b.dataset.bet);
el.querySelector('#spin').onclick = ()=>{
const bet = Math.max(1, parseInt(el.querySelector('#betAmt').value||'0'));
if(state.user.balance{
const n = Math.floor(Math.random()*37);
const color = n===0? 'GREEN' : (n%2? 'RED':'BLACK');
document.getElementById('wheel').textContent = n;
let win=0;
if(selection===color){ win = bet*2; }
if(selection==='GREEN' && n===0){ win = bet*14; }
if(win>0){ state.user.balance += bet + win; gameStatus.textContent=`Hit ${color} ${n}! You won ${formatINR(win)}.`; addTx('Roulette Win',win,'completed'); }
else{ gameStatus.textContent=`Hit ${color} ${n}. You lost.`; addTx('Roulette Bet',bet,'completed'); }
save(); updateBalance();
},900)
}
}
// Blackjack Demo
function blackjackDemo(){
const deck = []; 'A23456789TJQK'.split('').forEach(r=> ['♠','♥','♦','♣'].forEach(s=> deck.push(r+s)));
function val(card){ const r = card[0]; if('TJQK'.includes(r)) return 10; if(r==='A') return 11; return parseInt(r); }
function score(hand){ let s = hand.reduce((a,c)=> a+val(c),0); let aces = hand.filter(c=>c[0]==='A').length; while(s>21 && aces>0){ s-=10; aces--; } return s; }
function draw(){ return deck.splice(Math.floor(Math.random()*deck.length),1)[0] }
const el = document.createElement('div');
el.innerHTML = `
Deal
Hit
Stand
Target: 21
Dealer
You
Start Round
Cash Out
1.00×
`;
gameArea.appendChild(el);
const ctx = el.querySelector('#crash').getContext('2d');
const multEl = el.querySelector('#mult');
const start = el.querySelector('#start');
const cashout = el.querySelector('#cashout');
const betEl = el.querySelector('#bet');
let t=0, running=false, crashAt=0, cashed=false, bet=0, gain=0;
function draw(){ const w=ctx.canvas.width, h=ctx.canvas.height; ctx.clearRect(0,0,w,h); ctx.fillStyle='#0b0e29'; ctx.fillRect(0,0,w,h); ctx.strokeStyle='#7c4dff'; ctx.lineWidth=2; ctx.beginPath(); ctx.moveTo(40,h-30); for(let x=0;x< w-50;x++){ const tm = Math.max(0,t - (w-50-x)/30); const m = 1 + Math.pow(tm/160,1.9); const y = h-30 - Math.min(150, (m-1)*80); ctx.lineTo(40+x,y);} ctx.stroke(); ctx.fillStyle='#9aa3ff'; ctx.fillText('Multiplier',10,14); }
function tick(){ if(!running) return; t+=1; const m = 1 + Math.pow(t/160,1.9); multEl.textContent = m.toFixed(2)+'×'; draw(); if(m>=crashAt){ running=false; cashout.disabled=true; if(!cashed){ gameStatus.textContent=`Crashed at ${m.toFixed(2)}×. You lost.`; addTx('Crash Bet',bet,'completed'); } else { gameStatus.textContent=`Cashed at ${m.toFixed(2)}×. You won ${formatINR(gain)}.`; addTx('Crash Win',gain,'completed'); } save(); } else { requestAnimationFrame(tick); } }
start.onclick = ()=>{ if(running) return; bet = Math.max(1, parseInt(betEl.value||'0')); if(state.user.balance{ if(!running||cashed) return; const m = 1 + Math.pow(t/160,1.9); cashed=true; gain=Math.floor(bet*(m-1)); state.user.balance += bet + gain; updateBalance(); cashout.disabled=true; }
draw();
}
// ---------------- BRAND & LEGAL BUTTONS ----------------
document.getElementById('brandBtn').onclick = ()=>{
const newName = prompt('Enter your casino brand name:', brandName) || brandName; setBrand(newName.trim()||brandName);
}
// ---------------- INIT ----------------
document.getElementById('year').textContent = new Date().getFullYear();
setBrand('LuckyEmpire');
renderAuth();
renderGames();
mockLeader();
updateBalance();
const initial = (location.hash||'#home').replace('#',''); show(routes.includes(initial)? initial : 'home');
</script>