(function(){
"use strict";
/* ====== STATE ====== */
var G = {
status: 'loading',
gameId: 0,
startTime: 0,
duration: 10,
speed: 0.10,
crashPoint: 0,
mult: 1.00,
bets: {},
history: [],
pollTimer: null,
animFrame: null,
flyStart: 0,
localFlyStart: 0,
_alreadyCrashed: false
};
var API = 'api/lucky_jet_api.php';
var CS = (typeof window.CS !== 'undefined') ? window.CS : '₹';
/* ====== SAFE HELPERS ====== */
function safeUpdateBal(v){
if (typeof window.updateHeaderBalance === 'function') {
try { window.updateHeaderBalance(v); } catch(e){}
}
}
/* ====== TOAST ====== */
function showToast(msg) {
var t = document.createElement('div');
t.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:9999;background:#EF4444;color:#fff;padding:10px 24px;border-radius:10px;font-size:13px;font-weight:700;box-shadow:0 4px 20px rgba(0,0,0,.4);pointer-events:none;text-align:center;max-width:320px;';
t.textContent = msg;
document.body.appendChild(t);
setTimeout(function(){ t.style.transition='opacity .3s'; t.style.opacity='0'; setTimeout(function(){t.remove();},300); }, 2500);
}
/* ====== CANVAS ====== */
var cvs, ctx, W, H;
var jetImg, fireImg;
var jetLoaded = false, fireLoaded = false;
function initCanvas() {
cvs = document.getElementById('ljCvs');
if (!cvs) return;
ctx = cvs.getContext('2d');
resize();
window.addEventListener('resize', resize);
jetImg = document.getElementById('_ji');
fireImg = document.getElementById('_fi');
if (jetImg && jetImg.complete && jetImg.naturalWidth > 0) jetLoaded = true;
else if (jetImg) jetImg.onload = function(){ jetLoaded = true; };
if (fireImg && fireImg.complete && fireImg.naturalWidth > 0) fireLoaded = true;
else if (fireImg) fireImg.onload = function(){ fireLoaded = true; };
}
function resize() {
if (!cvs || !ctx) return;
var r = cvs.parentElement.getBoundingClientRect();
var dpr = window.devicePixelRatio || 1;
cvs.width = r.width * dpr;
cvs.height = r.height * dpr;
cvs.style.width = r.width + 'px';
cvs.style.height = r.height + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
W = r.width; H = r.height;
}
function clearCanvas() { if(ctx) ctx.clearRect(0,0,W,H); }
function drawFlying() {
if (!ctx) return;
clearCanvas();
var padL=2, padB=2, padT=15, padR=12;
var gW = W - padL - padR, gH = H - padT - padB;
var originX = padL, originY = H - padB;
var now = Date.now()/1000;
var jSize = 74;
var hoverX = W*0.60, hoverY = H*0.38;
var jetX, jetY;
if (G.localFlyStart) {
var introT = Math.min(1, (now - G.localFlyStart)/0.65);
var ease = 1 - Math.pow(1-introT,3);
jetX = originX + (hoverX-originX)*ease;
jetY = originY + (hoverY-originY)*ease;
if (introT >= 1) jetY = hoverY + Math.sin(now*4.5)*7;
} else { jetX = originX; jetY = originY; }
var tJet = Math.max(0, Math.min(1, (jetX-originX)/gW));
var steps = 80, curveEndX = originX, curveEndY = originY, i, t, x, yN;
for (i=1;i<=steps;i++){
t = (i/steps)*tJet;
curveEndX = originX + t*gW;
yN = Math.pow(t, 0.68);
curveEndY = originY - yN*gH*0.85;
}
var grad = ctx.createLinearGradient(0, originY, 0, padT);
grad.addColorStop(0,'rgba(126,58,242,0.03)');
grad.addColorStop(1,'rgba(126,58,242,0.22)');
ctx.beginPath();
ctx.moveTo(originX, originY);
for (i=1;i<=steps;i++){
t=(i/steps)*tJet; x=originX+t*gW; yN=Math.pow(t,0.68);
ctx.lineTo(x, originY - yN*gH*0.85);
}
ctx.lineTo(curveEndX, originY);
ctx.closePath();
ctx.fillStyle = grad; ctx.fill();
ctx.beginPath();
ctx.moveTo(originX, originY);
for (i=1;i<=steps;i++){
t=(i/steps)*tJet; x=originX+t*gW; yN=Math.pow(t,0.68);
ctx.lineTo(x, originY - yN*gH*0.85);
}
ctx.strokeStyle = '#A78BFA';
ctx.lineWidth = 2.5;
ctx.shadowColor = '#7E3AF2';
ctx.shadowBlur = 14;
ctx.stroke();
ctx.shadowBlur = 0;
if (jetLoaded && window.isAnimOn) {
var ff1 = 0.80 + 0.20*Math.abs(Math.sin(now*10));
var ff2 = 0.85 + 0.15*Math.abs(Math.cos(now*13));
var fireW = 44*ff1, fireH = 30*ff2;
ctx.save();
ctx.translate(jetX, jetY);
if (fireLoaded) {
ctx.save();
ctx.globalAlpha = 0.75 + 0.25*ff1;
ctx.translate(-jSize*0.30, -jSize*0.15);
ctx.rotate(2.50);
ctx.drawImage(fireImg, -fireW*0.15, -fireH*0.45, fireW*1.2, fireH*1.2);
ctx.restore();
}
ctx.save();
ctx.rotate(-0.12);
ctx.drawImage(jetImg, -jSize*0.35, -jSize*0.62, jSize, jSize);
ctx.restore();
ctx.restore();
} else {
ctx.beginPath();
ctx.arc(jetX, jetY, 13, 0, Math.PI*2);
ctx.fillStyle = '#A78BFA'; ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 10px Montserrat';
ctx.textAlign='center'; ctx.textBaseline='middle';
ctx.fillText('JET', jetX, jetY);
}
}
/* ====== HISTORY ====== */
function pillClass(v){ if(v>=10) return 'lj-pill-gold'; if(v>=2) return 'lj-pill-purple'; return 'lj-pill-blue'; }
function renderHistory(arr){
if (!arr || !arr.length) return;
G.history = arr;
var strip = document.getElementById('ljPills');
var grid = document.getElementById('ljHistGrid');
var h = '';
arr.forEach(function(v){ h += '
'+v.toFixed(2)+'x
'; });
if (strip) strip.innerHTML = h;
if (grid) grid.innerHTML = h;
}
/* ====== BET UI ====== */
function showBtn(panel, which) {
['Bet','Cancel','Wait','Wd'].forEach(function(b){
var el = document.getElementById('ljBtn'+b+panel);
if (el) el.style.display = 'none';
});
var map = {bet:'Bet', cancel:'Cancel', wait:'Wait', withdraw:'Wd'};
var el = document.getElementById('ljBtn'+(map[which]||'Bet')+panel);
if (el) el.style.display = 'flex';
}
function updateWdAmount(panel, amount) {
var el = document.getElementById('ljWA'+panel);
if (el) el.innerHTML = amount.toFixed(2) + ' ' + CS;
}
/* FIX: don't delete local bet that server hasn't ack'd yet */
function restoreBets(serverBets) {
var mapped = {};
if (serverBets && serverBets.length) {
serverBets.forEach(function(b){
var sid = b.section_id || 1;
mapped[sid] = {
bet_id: b.bet_id,
amount: parseFloat(b.amount),
game_id: parseInt(b.game_id,10) || 0
};
});
}
[1,2].forEach(function(p){
if (mapped[p]) {
G.bets[p] = mapped[p];
} else if (G.bets[p] && G.bets[p]._pending) {
// keep pending bet until server ack
} else {
delete G.bets[p];
}
});
}
/* ====== FAKE ALL-BETS ====== */
var fakeColors = ['#e11d48','#d97706','#0891b2','#16a34a','#7c3aed','#9333ea','#be123c','#b45309'];
function rName(n){ var r='',c='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; for(var i=0;i 50) allBetsData.pop();
renderFakeBets();
}
function startFakeBetPlacement(){
stopFakeTimers();
fakeAddTimer = setInterval(function(){
if (G.status !== 'loading') { clearInterval(fakeAddTimer); fakeAddTimer=null; return; }
addOneFakeBet();
}, 400 + Math.floor(Math.random()*500));
}
function simulateFakeCashouts(){
if (G.status !== 'flying') return;
var changed = false;
allBetsData.forEach(function(p){
if (!p.is_won && G.mult >= p.target_x) {
p.is_won = true; p.cashout_x = p.target_x; p.win = p.bet * p.target_x;
changed = true;
}
});
if (changed) renderFakeBets();
}
function startFakeCashouts(){
if (fakeCashTimer) clearInterval(fakeCashTimer);
fakeCashTimer = setInterval(function(){
if (G.status !== 'flying') { clearInterval(fakeCashTimer); fakeCashTimer=null; return; }
simulateFakeCashouts();
}, 200);
}
function stopFakeTimers(){
if (fakeAddTimer){ clearInterval(fakeAddTimer); fakeAddTimer=null; }
if (fakeCashTimer){ clearInterval(fakeCashTimer); fakeCashTimer=null; }
}
function renderFakeBets(){
var list = document.getElementById('allBetsList');
var countEl = document.getElementById('ljTotalBets');
if (!list) return;
if (countEl) countEl.textContent = fakeRoundTotal.toLocaleString();
var h = '';
allBetsData.forEach(function(p){
var pCls = p.is_won ? pillClass(p.cashout_x) : 'lj-pill-blue';
var multTxt = p.is_won ? p.cashout_x.toFixed(2)+'x' : '0.00x';
var winTxt = p.is_won ? p.win.toFixed(2)+' '+CS : '-';
var winCls = p.is_won ? '' : ' none';
h += ''
+ '
'+p.name.substring(0,2)+'
'+p.name+'
'
+ '
'+p.bet.toFixed(2)+'
'
+ '
'
+ '
'+winTxt+'
';
});
list.innerHTML = h;
}
var _lastFakeState = '';
setInterval(function(){
if (G.status==='loading' && _lastFakeState!=='loading'){ generateInitialFakeBets(); startFakeBetPlacement(); }
if (G.status==='flying' && _lastFakeState!=='flying') { stopFakeTimers(); startFakeCashouts(); }
if (G.status==='crashed' && _lastFakeState!=='crashed'){ stopFakeTimers(); renderFakeBets(); }
_lastFakeState = G.status;
}, 500);
/* ====== WIN POPUP ====== */
function showWinPop(amount, mult){
var box = document.getElementById('ljPops');
if (!box) return;
var div = document.createElement('div');
div.className = 'lj-pop';
div.innerHTML = 'Withdrawal at
x'+mult.toFixed(2)+'
'+amount.toFixed(2)+' '+CS+'
You Win!
';
box.appendChild(div);
setTimeout(function(){ if(div.parentNode) div.parentNode.removeChild(div); }, 3200);
if (window.isSoundOn){
var sn = document.getElementById('sWn');
if(sn){ sn.currentTime=0; sn.play().catch(function(){}); }
}
}
/* ====== ACTIONS ====== */
window.ljAction = function(panel, type){
if (type === 'bet') {
var amt = parseFloat(document.getElementById('ljA'+panel).value) || 8;
if (amt < 8) amt = 8;
if (amt > 25000) amt = 25000;
// optimistic pending bet so restoreBets won't wipe it before server ack
G.bets[panel] = { bet_id:0, amount:amt, game_id:0, _pending:true };
$.post(API, {action:'bet', amount:amt, section_id:panel}, function(r){
if (r.status==='success') {
G.bets[panel] = {bet_id:r.bet_id, amount:amt, game_id:r.game_id};
safeUpdateBal(r.new_balance);
if (G.status === 'flying' && r.game_id === 0) showBtn(panel,'wait');
else showBtn(panel,'cancel');
} else {
delete G.bets[panel];
showBtn(panel,'bet');
showToast(r.message==='insufficient_balance' ? 'Insufficient balance!' : (r.message||'Bet failed'));
}
}, 'json').fail(function(xhr){
delete G.bets[panel];
showBtn(panel,'bet');
try { var r=JSON.parse(xhr.responseText); showToast(r.message==='insufficient_balance'?'Insufficient balance!':(r.message||'Bet failed')); }
catch(e){ showToast('Network error'); }
});
}
else if (type === 'cancel') {
var b = G.bets[panel];
if (!b || !b.bet_id) return;
$.post(API, {action:'cancel_bet', bet_id:b.bet_id}, function(r){
if (r.status==='success'){
delete G.bets[panel];
safeUpdateBal(r.new_balance);
showBtn(panel,'bet');
} else { showToast(r.message||'Cancel failed'); }
}, 'json').fail(function(xhr){
try { var r=JSON.parse(xhr.responseText); showToast(r.message||'Cancel failed'); } catch(e){ showToast('Network error'); }
});
}
else if (type === 'withdraw') {
var b2 = G.bets[panel];
if (!b2 || !b2.bet_id || b2._cashing) return;
b2._cashing = true;
$.post(API, {action:'cashout', bet_id:b2.bet_id}, function(r){
if (r.status==='success'){
showWinPop(parseFloat(r.win_amount), parseFloat(r.multiplier));
delete G.bets[panel];
safeUpdateBal(r.new_balance);
showBtn(panel,'bet');
} else {
if (b2) b2._cashing = false;
if (r.message!=='crashed' && r.message!=='not_flying') showToast(r.message||'Cashout failed');
}
}, 'json').fail(function(xhr){
if (b2) b2._cashing = false;
try { var r=JSON.parse(xhr.responseText); showToast(r.message||'Cashout failed'); } catch(e){ showToast('Network error'); }
});
}
};
/* ====== POLL ====== */
var prevStatus = '';
function poll(){
$.post(API, {action:'check_game'}, function(r){
if (r && r.status){
G.status = r.status;
G.gameId = r.game_id;
G.startTime = r.start_time;
G.duration = r.duration;
G.speed = r.speed;
G.crashPoint = r.crash_point;
if (r.history) renderHistory(r.history);
restoreBets(r.my_bets || []);
if (r.status==='loading') handleLoading(r);
else if (r.status==='flying') handleFlying(r);
else if (r.status==='crashed') handleCrashed(r);
prevStatus = r.status;
}
if (r && r.new_balance !== undefined) safeUpdateBal(r.new_balance);
}, 'json').always(function(){
G.pollTimer = setTimeout(poll, 1000);
});
}
function handleLoading(r){
window._ljRoundStatus = 'loading';
if (typeof window.syncBgMusic === 'function') window.syncBgMusic();
G._alreadyCrashed = false;
$('#ljLoader').show(); $('#ljMult').hide(); $('#ljFlew').hide();
clearCanvas();
var pct = Math.min(100, ((r.elapsed||0) / r.duration) * 100);
$('#ljBar').css('width', pct+'%');
[1,2].forEach(function(p){
if (G.bets[p]) showBtn(p, G.bets[p]._pending ? 'cancel' : 'cancel');
else showBtn(p,'bet');
});
[1,2].forEach(function(p){
var ab = document.getElementById('ljAb'+p);
if (ab && ab.checked && !G.bets[p] && prevStatus === 'crashed') ljAction(p,'bet');
});
}
function handleFlying(r){
window._ljRoundStatus = 'flying';
if (typeof window.syncBgMusic === 'function') window.syncBgMusic();
$('#ljLoader').hide(); $('#ljFlew').hide(); $('#ljMult').show();
if (prevStatus === 'loading' || prevStatus === '') {
var serverElapsed = Math.max(0, (Date.now()/1000) - r.start_time);
if (prevStatus === '' && serverElapsed > 1) {
G.localFlyStart = Date.now()/1000 - 10;
} else {
G.localFlyStart = Date.now()/1000;
}
if (window.isSoundOn && prevStatus === 'loading') {
var s = document.getElementById('sSt');
if(s){ s.currentTime=0; s.play().catch(function(){}); }
}
}
var elapsed = Math.max(0, (Date.now()/1000) - r.start_time);
G.mult = Math.exp(G.speed * elapsed);
if (G.crashPoint > 0 && G.mult >= G.crashPoint) G.mult = G.crashPoint;
$('#ljMult').text('x' + G.mult.toFixed(2));
drawFlying();
[1,2].forEach(function(p){
var b = G.bets[p];
if (b){
if (b.game_id == G.gameId){
var wa = b.amount * G.mult;
updateWdAmount(p, wa);
showBtn(p,'withdraw');
var ac = document.getElementById('ljAc'+p);
var acI = document.getElementById('ljAcI'+p);
if (ac && ac.checked && acI && !b._autoTried){
var target = parseFloat(acI.value) || 2;
if (G.mult >= target){ b._autoTried = true; ljAction(p,'withdraw'); }
}
} else {
showBtn(p,'wait');
}
} else {
showBtn(p,'bet');
}
});
if (G.animFrame) cancelAnimationFrame(G.animFrame);
flyAnimate();
}
function flyAnimate(){
if (G.status !== 'flying') return;
var elapsed = Math.max(0, (Date.now()/1000) - G.startTime);
G.mult = Math.exp(G.speed * elapsed);
if (G.crashPoint > 0 && G.mult >= G.crashPoint){
if (G._alreadyCrashed) return;
G._alreadyCrashed = true;
G.mult = G.crashPoint;
G.status = 'crashed';
window._ljRoundStatus = 'crashed';
if (typeof window.syncBgMusic === 'function') window.syncBgMusic();
if (G.animFrame){ cancelAnimationFrame(G.animFrame); G.animFrame=null; }
G.localFlyStart = 0;
$('#ljLoader').hide(); $('#ljMult').hide(); $('#ljFlew').show();
$('#ljFlewM').text('x' + G.crashPoint.toFixed(2));
clearCanvas();
if (window.isSoundOn){
var sn = document.getElementById('sAw');
if(sn){ sn.currentTime=0; sn.play().catch(function(){}); }
}
[1,2].forEach(function(p){
if (G.bets[p] && G.bets[p].game_id == G.gameId) delete G.bets[p];
showBtn(p,'bet');
});
return;
}
$('#ljMult').text('x' + G.mult.toFixed(2));
drawFlying();
[1,2].forEach(function(p){
if (G.bets[p] && G.bets[p].game_id == G.gameId){
updateWdAmount(p, G.bets[p].amount * G.mult);
}
});
G.animFrame = requestAnimationFrame(flyAnimate);
}
function handleCrashed(r){
window._ljRoundStatus = 'crashed';
if (typeof window.syncBgMusic === 'function') window.syncBgMusic();
if (G.animFrame){ cancelAnimationFrame(G.animFrame); G.animFrame=null; }
G.localFlyStart = 0;
$('#ljLoader').hide(); $('#ljMult').hide(); $('#ljFlew').show();
$('#ljFlewM').text('x' + r.crash_point.toFixed(2));
clearCanvas();
if (prevStatus === 'flying' && !G._alreadyCrashed && window.isSoundOn){
var sn = document.getElementById('sAw');
if(sn){ sn.currentTime=0; sn.play().catch(function(){}); }
}
G._alreadyCrashed = true;
[1,2].forEach(function(p){
if (G.bets[p] && G.bets[p].game_id == G.gameId) delete G.bets[p];
showBtn(p,'bet');
});
}
/* ====== INIT ====== */
$(function(){
initCanvas();
generateInitialFakeBets();
showBtn(1,'bet'); showBtn(2,'bet');
poll();
});
})();