`;
document.body.appendChild(timeoutModal);
// --- Add Listeners for the Timeout Modal ---
document.getElementById('timeout-cancel-btn').onclick = () => {
document.getElementById('timeout-modal-overlay').style.display = 'none';
};
document.getElementById('timeout-confirm-btn').onclick = async () => {
const overlay = document.getElementById('timeout-modal-overlay');
const uid = overlay.dataset.targetUid;
const username = overlay.dataset.targetName;
const value = parseInt(document.getElementById('timeout-value').value, 10);
const unit = document.getElementById('timeout-unit').value;
if (!uid || !username || !value || value < 1) {
alert("Invalid timeout value.");
return;
}
let mins = value;
if (unit === 'hours') mins *= 60;
if (unit === 'days') mins *= 1440; // 60 * 24
const until = Date.now() + mins * 60 * 1000;
try {
await firebase.database().ref(`chatPunishments/${uid}`).set({
type: "timeout",
until,
by: firebase.auth().currentUser.uid,
name: username
});
await firebase.database().ref("slitherChat").push({
uid: "system",
name: "System",
text: `${username} was timed out from chat for ${value} ${unit}.`,
time: Date.now(),
chatNameColor: "#e91e63"
});
alert(`${username} timed out successfully.`);
overlay.style.display = 'none';
document.getElementById('profile-popup')?.remove();
} catch(err) {
alert(`Failed to timeout user: ${err.message}`);
}
};
}
// --- START: FINAL GAME SUITE MODAL (with Blackjack) ---
if (!document.getElementById('game-modal-overlay')) {
const gameModalHTML = `
Your REP: Loading...
Blackjack
Dealer's Hand (0)
Your Hand (0)
`;
const tempDiv = document.createElement('div');
tempDiv.innerHTML = gameModalHTML;
document.body.appendChild(tempDiv.firstElementChild);
// Copy original game content into the new structure
document.getElementById('game-spinner').innerHTML = `
Daily REP Spinner
0
`;
// ** NEW 3x3 SLOT MACHINE HTML **
document.getElementById('game-slots').innerHTML = `
";
}
// Full Menu
menu.style.width = state.uiLayout.menu.width !== null ? `${state.uiLayout.menu.width}px` : '480px'; // Wider full menu
let menuHtml = `
${arrow} Menu Customization
`;
menuHtml += `
${state.showMovement ? '▼' : '▶'} Movement
${state.keybinds.circleRestriction.toUpperCase()}: Circle Restrict:${state.features.circleRestriction ? 'ON' : 'OFF'}
${state.keybinds.circleSmaller.toUpperCase()}/${state.keybinds.circleLarger.toUpperCase()}: Circle Size:${state.circleRadius}px
(Developers will NEVER ask for money in chat. Beware of Scammers.)
Press ${state.keybinds.toggleMenu.toUpperCase()} to toggle | DSC.GG/143X | ${state.keybinds.screenshot.toUpperCase()} Screenshot
Made by: dxxthly. & waynesg
`;
if (menuContentArea) menuContentArea.innerHTML = menuHtml;
// Event listeners for newly created elements
setTimeout(() => {
// Customization Toggle
const custToggle = document.getElementById('customization-toggle');
if (custToggle) {
custToggle.onclick = () => {
state.showCustomization = !state.showCustomization;
sessionStorage.setItem('showCustomization', state.showCustomization.toString());
updateMenu();
};
}
// Toggle for Challenges
const challengesToggle = document.getElementById('challenges-toggle');
if (challengesToggle) {
challengesToggle.onclick = () => {
state.showChallenges = !state.showChallenges;
updateMenu();
};
}
// Ensure this is in your updateMenu function's setTimeout block
const foodBotBtn = document.getElementById('food-bot-toggle');
if (foodBotBtn) {
foodBotBtn.onclick = () => {
state.features.foodBot = !state.features.foodBot;
if (state.features.foodBot) {
state.features.neonLine = false; // Turn off manual line
runFoodBot();
} else {
// Send stop command
window.postMessage({ type: "143X_BOT_STOP" }, "*");
}
updateMenu();
};
}
// 1. Toggle for the new Menu Section
const extraModsToggle = document.getElementById('extra-mods-toggle');
if (extraModsToggle) {
extraModsToggle.onclick = () => {
// IMPORTANT: Make sure showExtraMods is in your "const state = { ... }" at the top!
state.showExtraMods = !state.showExtraMods;
updateMenu();
};
}
// 2. Disco Skin Button
const discoBtn = document.getElementById('disco-skin-btn');
if (discoBtn) {
discoBtn.onclick = () => {
state.features.discoSkin = !state.features.discoSkin;
toggleDiscoSkin();
updateMenu();
};
}
// Matrix HUD Button
const matrixBtn = document.getElementById('matrix-hud-btn');
if (matrixBtn) {
matrixBtn.onclick = () => {
state.features.matrixHUD = !state.features.matrixHUD;
updateMenu();
};
}
// The Glitcher Button
const glitchBtn = document.getElementById('glitcher-mode-btn');
if (glitchBtn) {
glitchBtn.onclick = () => {
state.features.glitcherMode = !state.features.glitcherMode;
updateMenu();
};
}
// 4. Fake Size Button
const fakeBtn = document.getElementById('fake-size-btn');
if (fakeBtn) {
fakeBtn.onclick = () => {
state.features.fakeSize = !state.features.fakeSize;
toggleFakeSize();
updateMenu();
};
// New Feature Listeners
const radarBtn = document.getElementById('tactical-radar-btn');
if (radarBtn) {
radarBtn.onclick = () => {
state.features.tacticalRadar = !state.features.tacticalRadar;
updateMenu();
};
}
const boostMonBtn = document.getElementById('boost-monitor-btn');
if (boostMonBtn) {
boostMonBtn.onclick = () => {
state.features.boostMonitor = !state.features.boostMonitor;
updateMenu();
};
}
const preyPredBtn = document.getElementById('prey-pred-btn');
if (preyPredBtn) {
preyPredBtn.onclick = () => {
state.features.preyPredictor = !state.features.preyPredictor;
updateMenu();
};
}
}
// --- GAMES LEADERBOARD LOGIC ---
const gamesLbBtn = document.getElementById('games-leaderboard-btn');
if (gamesLbBtn) {
gamesLbBtn.onclick = () => {
// 1. Create Modal if missing
if (!document.getElementById('games-leaderboard-modal')) {
const modal = document.createElement('div');
modal.id = 'games-leaderboard-modal';
modal.style = `display:none; position:fixed; top:0; left:0; width:100vw; height:100vh; z-index:10015; background:rgba(0,0,0,0.85); align-items:center; justify-content:center; font-family: 'Segoe UI', Arial, sans-serif;`;
modal.innerHTML = `
';
return;
}
let players = [];
// 2. Process data into a temporary list
snap.forEach(child => {
const val = child.val();
// Handle if data is just a number (Legacy) or object (New)
const score = (typeof val === 'object' && val.score) ? val.score : (typeof val === 'number' ? val : 0);
const storedName = (typeof val === 'object' && val.name) ? val.name : null;
players.push({ uid: child.key, score: score, name: storedName });
});
// 3. Sort High to Low
players.sort((a, b) => b.score - a.score);
// 4. Fetch Names for "Unknown" players from Online Users
// We run a lookup for every player to get the most up-to-date name
const namePromises = players.map(async (p) => {
// If we already have a saved name, use it, but check online status to get color/vip status if possible
const userSnap = await firebase.database().ref(`onlineUsers/${p.uid}`).once('value');
if (userSnap.exists()) {
const userData = userSnap.val();
p.name = userData.name || p.name || "Unknown"; // Update name from live profile
p.color = userData.chatNameColor || '#fff'; // Get their chat color
p.isOnline = true;
} else {
p.name = p.name || "Offline User"; // Fallback if no stored name and not online
p.color = '#aaa';
p.isOnline = false;
}
return p;
});
await Promise.all(namePromises);
// 5. Render HTML
let html = '';
players.forEach((p, i) => {
const rank = i + 1;
let rankColor = '#ddd';
let bg = 'rgba(255,255,255,0.05)';
let icon = '';
if (rank === 1) { rankColor = '#FFD700'; bg = 'linear-gradient(90deg, rgba(255, 215, 0, 0.15), transparent)'; icon = '👑 '; }
else if (rank === 2) { rankColor = '#C0C0C0'; icon = '🥈 '; }
else if (rank === 3) { rankColor = '#CD7F32'; icon = '🥉 '; }
// Sanitize name to prevent HTML injection
const cleanName = p.name.replace(/[<>]/g, '');
let scoreSuffix = ' Wins';
if (gameType === 'slots' || gameType === 'roulette') scoreSuffix = ' REP';
html += `
';
try {
// Fetch Top 50 for the selected game
const snap = await firebase.database().ref(`minigameHighscores/${gameType}`)
.orderByChild('score')
.limitToLast(50)
.once('value');
if (!snap.exists()) {
content.innerHTML = '
No records found yet. Play to be the first!
';
return;
}
let players = [];
snap.forEach(child => {
const val = child.val();
// Handle both object format {name, score} and simple format if legacy
const score = typeof val === 'object' ? val.score : val;
const name = typeof val === 'object' ? val.name : 'Unknown';
const uid = child.key;
players.push({ uid, name, score });
});
// Sort descending (High to Low)
players.sort((a, b) => b.score - a.score);
let html = '';
players.forEach((p, i) => {
const rank = i + 1;
let color = '#ddd';
let bg = 'rgba(255,255,255,0.05)';
if (rank === 1) { color = '#FFD700'; bg = 'rgba(255, 215, 0, 0.1)'; }
else if (rank === 2) { color = '#C0C0C0'; }
else if (rank === 3) { color = '#CD7F32'; }
let scoreLabel = 'Wins';
if (gameType === 'slots') scoreLabel = 'REP';
if (gameType === 'roulette') scoreLabel = 'REP';
html += `
Click "Set" to rebind. Press ${(state.keybinds.toggleKeybinds || '-').toUpperCase()} to toggle all mod keybinds.
`;
setTimeout(() => {
// Back button listener is set when header is created above
document.querySelectorAll('.set-keybind-btn').forEach(btn => {
btn.onclick = () => openKeybindModal(btn.dataset.action);
});
}, 0);
}
function applyBackground() {
const defaultBgUrl = 'https://slither.io/s2/bg54.jpg';
const blackBgDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
// LOGIC FIX: Check Black BG first, then Custom URL, then Default
if (state.features.blackBg) {
window.__customBgUrlCurrent = blackBgDataUrl;
} else if (state.savedBgUrl && state.savedBgUrl.trim() !== '') {
window.__customBgUrlCurrent = state.savedBgUrl;
} else {
window.__customBgUrlCurrent = defaultBgUrl;
}
// Force game to redraw background immediately
if (window.resize) {
window.resize();
}
}
// === GAME STATE DETECTION ===
// === GAME STATE DETECTION ===
function checkGameState() {
const gameCanvas = document.querySelector('canvas');
const loginForm = document.getElementById('login');
state.isInGame = !!(gameCanvas && gameCanvas.style.display !== 'none' && (!loginForm || loginForm.style.display === 'none'));
setTimeout(checkGameState, 1000);
}
// === CIRCLE RESTRICTION VISUAL ===
// === CIRCLE RESTRICTION VISUAL ===
function drawCircleRestriction() {
if (state.features.circleRestriction && state.isInGame) {
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
// Ensure circleVisual is defined and accessible (it's created globally in your script)
if (circleVisual) {
circleVisual.style.left = `${centerX}px`;
circleVisual.style.top = `${centerY}px`;
circleVisual.style.width = `${state.circleRadius * 2}px`;
circleVisual.style.height = `${state.circleRadius * 2}px`;
circleVisual.style.display = 'block';
}
} else {
if (circleVisual) {
circleVisual.style.display = 'none';
}
}
requestAnimationFrame(drawCircleRestriction);
}
// REMOVE the standalone drawCircleRestriction(); call from here if it exists. It will be called once at the end.
document.addEventListener('keydown', function (e) {
const activeEl = document.activeElement;
// --- THIS IS THE FIX ---
// Check if the user is currently focused on ANY input, textarea,
// or if the keybind rebinding modal is active.
if ( (activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA' || activeEl.isContentEditable)) || waitingForKeybind ) {
// If they are typing, we do nothing and let the browser handle the key press.
// We only make an exception for the 'Escape' key to allow blurring the input.
if (e.key === 'Escape' && activeEl) {
activeEl.blur();
}
return; // This is the most important part: it stops the function right here.
}
// --- END OF FIX ---
// If we get past the check above, it means the user is NOT typing in an input box,
// so we can now safely process our mod's keybinds.
// Handle arrow keys first (for AFK mode)
if (e.key === 'ArrowLeft') window.l = true;
if (e.key === 'ArrowRight') window.r = true;
const key = e.key.toLowerCase() === " " ? "space" : e.key.toLowerCase();
const binds = state.keybinds;
// Universal toggles (these should work even if other keybinds are off)
if (key === binds.toggleMenu) {
state.menuVisible = !state.menuVisible;
menu.style.display = state.menuVisible ? 'block' : 'none';
if (state.menuVisible && typeof updateMenu === "function") updateMenu();
e.preventDefault();
return;
}
if (key === binds.toggleKeybinds) {
state.features.keybindsEnabled = !state.features.keybindsEnabled;
if (typeof updateMenu === "function") updateMenu();
e.preventDefault();
return;
}
if (!state.features.keybindsEnabled) return;
if (key === binds.chatEnabled && state.features.chatVisible) {
const chatInput = document.getElementById('mod-menu-chat-input');
if (chatInput) {
chatInput.focus();
e.preventDefault();
}
return;
}
let actionTaken = false;
switch (key) {
case '=':
state.features.blackBg = !state.features.blackBg; // This toggles the switch ON/OFF
applyBackground(); // This tells the game to update the background
actionTaken = true;
break;
case binds.toggleEsp:
state.features.esp = !state.features.esp;
// Send signal to the brain
window.postMessage({ type: "143X_ESP_TOGGLE", val: state.features.esp }, "*");
if (typeof updateMenu === "function") updateMenu();
actionTaken = true;
break;
case binds.circleRestriction:
state.features.circleRestriction = !state.features.circleRestriction;
actionTaken = true;
break;
case binds.circleSmaller:
state.circleRadius = Math.max(config.minCircleRadius, state.circleRadius - config.circleRadiusStep);
actionTaken = true;
break;
case binds.circleLarger:
state.circleRadius = Math.min(config.maxCircleRadius, state.circleRadius + config.circleRadiusStep);
actionTaken = true;
break;
case binds.autoCircle:
state.features.autoCircle = !state.features.autoCircle;
if (state.features.autoCircle && !autoCircleRAF) {
autoCircleRAF = requestAnimationFrame(autoCircle);
} else if (autoCircleRAF) {
cancelAnimationFrame(autoCircleRAF);
autoCircleRAF = null;
}
if (typeof updateMenu === "function") updateMenu();
break;
case binds.autoBoost:
state.features.autoBoost = !state.features.autoBoost;
if (typeof updateMenu === "function") updateMenu();
break;
case binds.fpsDisplay:
state.features.fpsDisplay = !state.features.fpsDisplay;
if (fpsDisplay) fpsDisplay.style.display = state.features.fpsDisplay ? 'block' : 'none';
actionTaken = true;
break;
case binds.deathSound:
state.features.deathSound = !state.features.deathSound;
actionTaken = true;
break;
case binds.showServer:
state.features.showServer = !state.features.showServer;
actionTaken = true;
break;
case binds.neonLine:
state.features.neonLine = !state.features.neonLine;
if (state.features.neonLine) {
neonLineActive = true; createNeonLineCanvas(); window.addEventListener('mousemove', neonLineDraw);
} else {
neonLineActive = false; if (neonCtx && neonCanvas) neonCtx.clearRect(0,0,neonCanvas.width, neonCanvas.height); window.removeEventListener('mousemove', neonLineDraw);
}
actionTaken = true;
break;
case binds.foodBot:
state.features.foodBot = !state.features.foodBot;
if (state.features.foodBot) {
// IMPORTANT: Turn off manual neon line to avoid conflict
state.features.neonLine = false;
if(neonCanvas) neonCanvas.style.display = 'none'; // Hide manual canvas
runFoodBot(); // Start the injected bot
} else {
// Show manual canvas again if needed
if(neonCanvas) neonCanvas.style.display = 'block';
runFoodBot(); // This will send the STOP signal
}
if (typeof updateMenu === "function") updateMenu();
actionTaken = true;
break;
case binds.zoomIn:
case binds.zoomOut:
if (state.isInGame) {
let idx = zoomSteps.findIndex(z => Math.abs(z - state.zoomFactor) < 1e-5);
if (idx === -1) idx = zoomSteps.reduce((best, z_1, i) => Math.abs(z_1 - state.zoomFactor) < Math.abs(zoomSteps[best] - state.zoomFactor) ? i : best, 0);
if (key === binds.zoomIn && idx > 0) idx--;
else if (key === binds.zoomOut && idx < zoomSteps.length - 1) idx++;
state.zoomFactor = zoomSteps[idx];
actionTaken = true;
}
break;
case binds.zoomReset:
if (state.isInGame) {
state.zoomFactor = 1.0;
actionTaken = true;
}
break;
case binds.autoRespawn:
state.features.autoRespawn = !state.features.autoRespawn;
if (state.features.autoRespawn) enableAutoRespawn(); else disableAutoRespawn();
actionTaken = true;
break;
case binds.screenshot:
if (state.isInGame) {
try {
const canvas = document.querySelector('canvas');
if (canvas) {
const dataURL = canvas.toDataURL();
const link = document.createElement('a');
link.href = dataURL;
link.download = `slither_screenshot_${Date.now()}.png`;
document.body.appendChild(link); link.click(); document.body.removeChild(link);
}
} catch (err) { alert('Screenshot failed: ' + err); }
}
actionTaken = true;
break;
case binds.spotify:
window.open("https://spti.fi/dxxthly", "_blank");
actionTaken = true;
break;
case binds.github: window.open('https://github.com/dxxthly', '_blank'); actionTaken = true; break;
case binds.discord: window.open('https://dsc.gg/143x', '_blank'); actionTaken = true; break;
case binds.godMode: window.open(config.godModeVideoURL, '_blank'); actionTaken = true; break;
case binds.reddit: if (binds.reddit) window.open('https://dxxthly.itch.io/abyssal-ascension', '_blank'); actionTaken = true; break;
case '1': case '2': case '3':
state.features.performanceMode = parseInt(key);
applyPerformanceMode();
actionTaken = true;
break;
}
if (actionTaken) {
if (typeof updateMenu === "function") {
updateMenu();
}
e.preventDefault();
}
});
document.addEventListener('keyup', function(e) {
if (e.key === 'ArrowLeft') window.l = false;
if (e.key === 'ArrowRight') window.r = false;
});
// === FORCED SERVER LOGIC ===
function applyForcedServer() {
try {
const savedForcedServer = localStorage.getItem('forcedServer');
if (!savedForcedServer) return;
const serverDetails = JSON.parse(savedForcedServer);
if (!serverDetails.ip || !serverDetails.port) { localStorage.removeItem('forcedServer'); return; }
window.forcing = true;
if (!window.bso) window.bso = {};
window.bso.ip = serverDetails.ip;
window.bso.po = parseInt(serverDetails.port, 10);
} catch (e) { console.error("Error applying forced server:", e); localStorage.removeItem('forcedServer'); }
}
function patchPlayButtons() {
const mainPlayBtn = document.getElementById('playh') || document.querySelector('.btn-play-guest') || document.querySelector('form .btn.btn-primary');
if (mainPlayBtn && !mainPlayBtn._patchedForceServer) { mainPlayBtn._patchedForceServer = true; mainPlayBtn.addEventListener('click', () => { setTimeout(applyForcedServer, 0); }, true); }
document.querySelectorAll('.btn-play-again, #play-again, .play_btn').forEach(playAgainBtn => { if (playAgainBtn && !playAgainBtn._patchedForceServer) { playAgainBtn._patchedForceServer = true; playAgainBtn.addEventListener('click', () => { setTimeout(applyForcedServer, 0); }, true); } });
}
// These calls below this function are important for its operation:
setInterval(patchPlayButtons, 1000);
applyForcedServer(); // Apply on load
// === AUTO CIRCLE (Bot Movement) ===
// autoCircleRAF is already declared globally
// autoCircleRAF is declared in the global-like scope of your IIFE
// === AUTO CIRCLE ===
function autoCircle() {
if (!state.features.autoCircle || !state.isInGame) { // Check isInGame from your state
if (autoCircleRAF) { // Ensure autoCircleRAF is declared in the script's scope
cancelAnimationFrame(autoCircleRAF);
autoCircleRAF = null;
}
// If the feature is ON in UI but conditions not met, update UI
if (state.features.autoCircle) {
state.features.autoCircle = false; // Correct the state
if (typeof updateMenu === "function") updateMenu();
}
return;
}
try {
state.autoCircleAngle += 0.025; // Your original speed
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const radius = state.circleRadius; // Your original radius logic
const moveX = centerX + Math.cos(state.autoCircleAngle) * radius;
const moveY = centerY + Math.sin(state.autoCircleAngle) * radius;
const canvas = document.querySelector('canvas');
if (canvas) {
const event = new MouseEvent('mousemove', {
clientX: moveX,
clientY: moveY,
bubbles: true
});
canvas.dispatchEvent(event);
}
} catch (err) {
console.error("Auto Circle error:", err); // Good to keep error logging
}
if (state.features.autoCircle) { // Keep requesting frame if feature is still on
autoCircleRAF = requestAnimationFrame(autoCircle);
} else { // Explicitly clear if toggled off elsewhere
if (autoCircleRAF) {
cancelAnimationFrame(autoCircleRAF);
autoCircleRAF = null;
}
}
}
// === SNAKE TRAIL DRAWING ===
function drawSnakeTrail() {
if (!state.features.snakeTrail || !state.snakeTrailPoints || !state.snakeTrailPoints.length) { // Removed isInGame check here, trail can be drawn if points exist
if (typeof clearTrailOverlay === "function") clearTrailOverlay(); // Call your clear function
return;
}
const overlay = createTrailOverlayCanvas(); // Use your (now updated) createTrailOverlayCanvas
if (!overlay) return;
const ctx = overlay.getContext('2d');
ctx.clearRect(0, 0, overlay.width, overlay.height);
const TRAIL_MAX_AGE = 1500;
const now = Date.now();
const viewX = window.snake ? window.snake.xx || 0 : 0;
const viewY = window.snake ? window.snake.yy || 0 : 0;
const viewZoom = window.gsc || 1;
// Use overlay center if trail is aligned to game canvas, else window center
const screenCenterX = overlay.width / 2;
const screenCenterY = overlay.height / 2;
ctx.save();
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.lineWidth = 8; // Your original lineWidth
ctx.shadowBlur = 12; // Your original shadowBlur
ctx.shadowColor = state.features.snakeTrailColor;
for (let i = 1; i < state.snakeTrailPoints.length; i++) {
const p1 = state.snakeTrailPoints[i-1];
const p2 = state.snakeTrailPoints[i];
const age = now - ((p1.time + p2.time) / 2);
const alpha = Math.max(0, 1 - age / TRAIL_MAX_AGE);
if (alpha <= 0) continue;
const deltaX1 = p1.x - viewX;
const deltaY1 = p1.y - viewY;
const screenX1 = screenCenterX + deltaX1 * viewZoom;
const screenY1 = screenCenterY + deltaY1 * viewZoom;
const deltaX2 = p2.x - viewX;
const deltaY2 = p2.y - viewY;
const screenX2 = screenCenterX + deltaX2 * viewZoom;
const screenY2 = screenCenterY + deltaY2 * viewZoom;
ctx.strokeStyle = hexToRgba(state.features.snakeTrailColor, alpha * 0.7);
ctx.beginPath();
ctx.moveTo(screenX1, screenY1);
ctx.lineTo(screenX2, screenY2);
ctx.stroke();
}
ctx.restore();
}
// === FOOD BOT V29: "APEX PREDATOR" (Aggressive Cut-offs + Feasting + Tactical ESP) ===
function runFoodBot() {
// 1. Sync State
if (document.getElementById('bot-brain-script')) {
const settings = {
type: state.features.foodBot ? "143X_BOT_START" : "143X_BOT_STOP",
colors: {
head: document.getElementById('esp-head-color')?.value || '#FF0000',
body: document.getElementById('esp-body-color')?.value || '#FF0000',
food: document.getElementById('esp-food-color')?.value || '#FFFFFF',
me: '#00FF00'
},
showStats: document.getElementById('show-stats-toggle')?.checked || false
};
window.postMessage(settings, "*");
return;
}
// 2. Inject The Apex Brain
const script = document.createElement('script');
script.id = 'bot-brain-script';
script.textContent = `
(function() {
console.log("143X Brain: V29 APEX Loaded");
// --- CONFIG ---
let espEnabled = false;
let botEnabled = false;
let showStats = true;
let colors = { head: '#FF0000', body: '#FF0000', food: '#FFFFFF', me: '#00FF00' };
// --- VISUAL STATE ---
let tick = 0;
let graphData = new Array(60).fill(0);
const canvas = document.createElement('canvas');
Object.assign(canvas.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', zIndex: '9000', pointerEvents: 'none' });
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
// --- LOGIC STATE ---
let currentTargetId = null;
let state = "IDLE";
function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
window.addEventListener('resize', resize);
resize();
// --- COMM ---
window.addEventListener("message", e => {
if(!e.data) return;
if(e.data.type === "143X_BOT_START") botEnabled = true;
if(e.data.type === "143X_BOT_STOP") { botEnabled = false; if(window.setAcceleration) window.setAcceleration(0); if(!espEnabled) ctx.clearRect(0,0,canvas.width,canvas.height); }
if(e.data.type === "143X_ESP_TOGGLE") { espEnabled = e.data.val; if(!espEnabled && !botEnabled) ctx.clearRect(0,0,canvas.width,canvas.height); }
if(e.data.type === "143X_UPDATE_CONFIG") { if(e.data.showStats !== undefined) showStats = e.data.showStats; }
if(e.data.colors) colors = e.data.colors;
});
// --- ALIGNMENT MATH ---
function toScreen(x, y) {
if(!window.mc) return {x:-9999, y:-9999};
let rect = window.mc.getBoundingClientRect();
let gsc = window.gsc || 1;
let gX = (window.mww2 || 0) + gsc * (x - (window.view_xx || 0));
let gY = (window.mhh2 || 0) + gsc * (y - (window.view_yy || 0));
return {
x: rect.left + (gX * (rect.width / window.mc.width)),
y: rect.top + (gY * (rect.height / window.mc.height))
};
}
function dist(x1, y1, x2, y2) { return Math.hypot(x2-x1, y2-y1); }
// --- VISUAL HELPERS ---
function drawBar(x, y, w, h, pct, color) {
ctx.fillStyle = "rgba(10,15,20,0.8)"; ctx.fillRect(x, y, w, h);
ctx.fillStyle = color; ctx.fillRect(x+1, y+1, (w-2)*Math.min(1, pct), h-2);
}
function drawReticle(x, y, rad, color, label) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(tick * 0.05);
ctx.strokeStyle = color; ctx.lineWidth = 2;
ctx.shadowBlur = 10; ctx.shadowColor = color;
// Tech Circle
ctx.setLineDash([5, 10]);
ctx.beginPath(); ctx.arc(0, 0, rad, 0, Math.PI*2); ctx.stroke();
ctx.setLineDash([]);
// Brackets
ctx.rotate(-tick * 0.1);
ctx.beginPath();
ctx.moveTo(-rad, -10); ctx.lineTo(-rad, 10);
ctx.moveTo(rad, -10); ctx.lineTo(rad, 10);
ctx.moveTo(-10, -rad); ctx.lineTo(10, -rad);
ctx.moveTo(-10, rad); ctx.lineTo(10, rad);
ctx.stroke();
if(label) {
ctx.rotate(0);
ctx.fillStyle = "#FFF"; ctx.font = "10px monospace"; ctx.textAlign = "center";
ctx.fillText(label, 0, rad + 15);
}
ctx.restore();
}
function drawTechBox(x, y, w, h, title) {
ctx.fillStyle = "rgba(12, 16, 24, 0.9)";
ctx.strokeStyle = "rgba(0, 255, 255, 0.5)";
ctx.lineWidth = 1;
ctx.fillRect(x, y, w, h);
ctx.strokeRect(x, y, w, h);
ctx.fillStyle = "rgba(0, 255, 255, 0.1)";
ctx.fillRect(x, y, w, 20); // Header
ctx.fillStyle = "#00FFFF";
ctx.font = "bold 11px monospace";
ctx.fillText(title, x + 10, y + 14);
}
function drawGraph(x, y, w, h, data) {
ctx.beginPath(); ctx.strokeStyle = "#00FF00"; ctx.lineWidth = 1;
let max = Math.max(...data, 100);
for(let i=0; i -50 && eScreen.x < canvas.width+50) {
// Health Bar
let sScore = Math.floor(15 * (s.sct + (s.fam||0)));
drawBar(eScreen.x - 20, eScreen.y - 35, 40, 4, Math.min(1, sSize/8), color);
// Name
ctx.fillStyle = "#FFF"; ctx.font = "bold 10px Arial"; ctx.textAlign = "center";
ctx.fillText((s.nk || "Anon") + " [" + sScore + "]", eScreen.x, eScreen.y - 40);
// Head Dot
ctx.beginPath(); ctx.fillStyle = color; ctx.arc(eScreen.x, eScreen.y, 5*sSize, 0, Math.PI*2); ctx.fill();
// Skeleton
if(isThreat) {
ctx.strokeStyle = color; ctx.lineWidth = 1;
ctx.beginPath();
let pts = s.pts;
for(let i=pts.length-1; i>=0; i-=5) {
let p = pts[i]; if(p.dying) continue;
let pPos = toScreen(p.xx, p.yy);
if(i===pts.length-1) ctx.moveTo(pPos.x, pPos.y); else ctx.lineTo(pPos.x, pPos.y);
}
ctx.stroke();
}
}
}
// BOT LOGIC: Collision Avoidance
if(botEnabled) {
let pts = s.pts;
for(let i=0; i 100) {
let eAng = s.ang;
// Check if we are generally moving in similar direction (dot product > 0)
let dot = Math.cos(myAngle) * Math.cos(eAng) + Math.sin(myAngle) * Math.sin(eAng);
if(dot > 0.3) {
// Calculate Intercept Point (Where their head will be)
let leadDist = 200 + (mySize * 50);
let intX = ex + Math.cos(eAng) * leadDist;
let intY = ey + Math.sin(eAng) * leadDist;
// Check if intercept point is closer to me than them (advantage)
let distMe = Math.hypot(intX - myHeadX, intY - myHeadY);
let distThem = Math.hypot(intX - ex, intY - ey);
if(distMe < distThem * 0.8) {
huntTarget = { x: intX, y: intY, snake: s };
}
}
}
}
}
// --- 2. DECISION ENGINE ---
if(botEnabled) {
let bestFood = null;
let maxScore = -Infinity;
// Find Food
if(window.foods) {
for(let f of window.foods) {
if(!f || !f.xx) continue;
let d = dist(myHeadX, myHeadY, f.xx, f.yy);
// Value size heavily, penalize distance
let score = (f.sz ** 3) / (d + 1);
if(score > maxScore) { maxScore = score; bestFood = f; }
}
}
// PRIORITY 1: SURVIVAL (Evasion)
if(dangerDetected) {
state = "EVADING";
forceX = avoidX;
forceY = avoidY;
shouldBoost = false; // Don't boost into walls
}
// PRIORITY 2: FEASTING (Dead Snake)
else if(bestFood && bestFood.sz > 12 && maxScore > 50) {
state = "FEASTING";
let dirX = bestFood.xx - myHeadX;
let dirY = bestFood.yy - myHeadY;
let mag = Math.hypot(dirX, dirY);
forceX = (dirX/mag) * 1000; // Strong pull
forceY = (dirY/mag) * 1000;
shouldBoost = mag > 100; // Boost to eat
let fPos = toScreen(bestFood.xx, bestFood.yy);
drawReticle(fPos.x, fPos.y, 30, "#FFA500", "FEAST");
}
// PRIORITY 3: HUNTING (Cut-off)
else if(huntTarget) {
state = "CUTTING OFF";
let dirX = huntTarget.x - myHeadX;
let dirY = huntTarget.y - myHeadY;
let mag = Math.hypot(dirX, dirY);
forceX = (dirX/mag) * 800;
forceY = (dirY/mag) * 800;
shouldBoost = true; // Aggressive
let tPos = toScreen(huntTarget.x, huntTarget.y);
drawReticle(tPos.x, tPos.y, 20, "#FF00FF", "KILL");
ctx.strokeStyle = "#FF00FF"; ctx.beginPath(); ctx.moveTo(myScreen.x, myScreen.y); ctx.lineTo(tPos.x, tPos.y); ctx.stroke();
}
// PRIORITY 4: FORAGING (Standard Food)
else if(bestFood) {
state = "FORAGING";
let dirX = bestFood.xx - myHeadX;
let dirY = bestFood.yy - myHeadY;
let mag = Math.hypot(dirX, dirY);
forceX = (dirX/mag) * 200;
forceY = (dirY/mag) * 200;
let fPos = toScreen(bestFood.xx, bestFood.yy);
ctx.strokeStyle = "rgba(0, 255, 0, 0.3)"; ctx.beginPath(); ctx.moveTo(myScreen.x, myScreen.y); ctx.lineTo(fPos.x, fPos.y); ctx.stroke();
}
// PRIORITY 5: WANDER
else {
forceX = Math.cos(me.ang) * 100;
forceY = Math.sin(me.ang) * 100;
}
// Execute Movement
let finalX = myHeadX + forceX;
let finalY = myHeadY + forceY;
let sTarget = toScreen(finalX, finalY);
window.xm = sTarget.x - canvas.width/2;
window.ym = sTarget.y - canvas.height/2;
if(window.setAcceleration) window.setAcceleration(shouldBoost ? 1 : 0);
}
// --- 3. HUD RENDER ---
if(botEnabled || espEnabled) {
let hudW = 240; let hudH = 160;
let hudX = canvas.width - hudW - 20;
let hudY = 100;
drawTechBox(hudX, hudY, hudW, hudH, "APEX SYSTEM V29");
ctx.fillStyle = "#FFF"; ctx.font = "12px monospace"; ctx.textAlign = "left";
ctx.fillText("MODE : " + (botEnabled ? "AUTOPILOT" : "MANUAL"), hudX + 15, hudY + 40);
ctx.fillStyle = state === "EVADING" ? "#FF0044" : (state === "FEASTING" ? "#FFA500" : (state === "CUTTING OFF" ? "#FF00FF" : "#00FF00"));
ctx.fillText("ACTION : " + state, hudX + 15, hudY + 55);
ctx.fillStyle = "#888";
ctx.fillText("GROWTH VELOCITY", hudX + 15, hudY + 80);
drawGraph(hudX + 15, hudY + 90, hudW - 30, 50, graphData);
}
}, 40);
})();
`;
document.body.appendChild(script);
// Init Config
setTimeout(() => {
const headC = document.getElementById('esp-head-color')?.value || '#FF0000';
const bodyC = document.getElementById('esp-body-color')?.value || '#FF0000';
const foodC = document.getElementById('esp-food-color')?.value || '#FFFFFF';
const showStats = document.getElementById('show-stats-toggle')?.checked;
window.postMessage({
colors: { head: headC, body: bodyC, food: foodC, me: '#00FF00' },
showStats: showStats
}, "*");
if(state.features.foodBot) window.postMessage({type: "143X_BOT_START"}, "*");
}, 500);
}
// === AUTO BOOST ===
function autoBoost() {
if (!state.features.autoBoost || !state.isInGame) {
if (state.boosting) {
state.boosting = false;
if (typeof window.setAcceleration === 'function') window.setAcceleration(0);
document.dispatchEvent(new KeyboardEvent('keyup', { key: ' ' }));
}
return;
}
if (!state.boosting) {
state.boosting = true;
if (typeof window.setAcceleration === 'function') window.setAcceleration(1);
document.dispatchEvent(new KeyboardEvent('keydown', { key: ' ' }));
}
}
function autoBoostLoop() {
autoBoost();
setTimeout(autoBoostLoop, 100);
}
autoBoostLoop();
function fpsCounter() {
state.fpsFrames++;
const now = Date.now();
if (now - state.fpsLastCheck >= 1000) {
state.fps = state.fpsFrames;
state.fpsFrames = 0;
state.fpsLastCheck = now;
if (state.features.fpsDisplay && fpsDisplay) { // Check if fpsDisplay exists
fpsDisplay.textContent = `FPS: ${state.fps}`;
}
// Update ping in simplified menu status if visible
if (state.simplified && state.menuVisible) {
const pingValueDisplay = document.getElementById("ping-value-simplified");
if (pingValueDisplay) pingValueDisplay.textContent = `${state.ping} ms`;
}
}
requestAnimationFrame(fpsCounter);
}
fpsCounter();
// === NEW: VISUAL FILTERS LOGIC (Corrected to target the whole screen) ===
function applyVisualFilter() {
// Instead of targeting just the canvas, we will target the entire page body.
const targetElement = document.body;
let filterValue = '';
switch (state.features.visualMode) {
case 'invert':
filterValue = 'invert(1)';
break;
case 'contrast':
filterValue = 'contrast(1.75)';
break;
case 'grayscale':
filterValue = 'grayscale(1)';
break;
case 'sepia':
filterValue = 'sepia(1)';
break;
case 'none':
default:
filterValue = 'none';
break;
}
// Apply the filter to the entire body element.
targetElement.style.filter = filterValue;
}
function deathSoundObserver() { /* ... unchanged ... */ }
state.deathSound.preload = 'auto';
state.deathSound.load();
state.deathSound.addEventListener('ended', () => { state.deathSound.currentTime = 0; });
deathSoundObserver();
function applyPerformanceMode() {
if (typeof window !== "undefined") {
switch (state.features.performanceMode) {
case 1: window.want_quality = 0; window.high_quality = false; window.render_mode = 1; break;
case 2: window.want_quality = 1; window.high_quality = false; window.render_mode = 2; break;
case 3: window.want_quality = 2; window.high_quality = true; window.render_mode = 2; break;
}
}
updateMenu(); // Update menu to reflect change
}
applyPerformanceMode(); // Initial call
// === ZOOM LOCK ===
// === ZOOM LOCK ===
function zoomLockLoop() {
// Stop this from running on Damnbruh.com
if (isDamnBruh) return;
if (typeof window.gsc !== 'undefined' && state.isInGame) { // Check isInGame // Check isInGame
if (Math.abs(window.gsc - state.zoomFactor) > 0.001) { // Avoid tiny floating point updates
window.gsc = state.zoomFactor;
}
}
requestAnimationFrame(zoomLockLoop);
}
// REMOVE the standalone zoomLockLoop(); call from here. It will be called once at the end.
function pingLoop() { // Simplified ping display, mainly for simplified menu status
let currentPing = 0;
if (window.lagging && typeof window.lagging === "number") currentPing = Math.round(window.lagging);
else if (window.lag && typeof window.lag === "number") currentPing = Math.round(window.lag);
state.ping = currentPing;
// Ping display element outside menu is removed, rely on status in menu
// If you want it back, recreate it and update here:
// const pingDisplayEl = document.getElementById('ping-display');
// if (pingDisplayEl) pingDisplayEl.textContent = `Ping: ${currentPing} ms`;
// This is now handled in fpsCounter to reduce DOM updates
// if (state.simplified && state.menuVisible) {
// const pingValueDisplay = document.getElementById("ping-value-simplified");
// if (pingValueDisplay) pingValueDisplay.textContent = `${currentPing} ms`;
// }
setTimeout(pingLoop, 500);
}
pingLoop();
function clearTrailOverlay() {
const overlay = document.getElementById('snake-trail-overlay');
if (overlay) {
const ctx = overlay.getContext('2d');
ctx.clearRect(0, 0, overlay.width, overlay.height);
overlay.style.display = 'none'; // <--- Hide the overlay when trail is off
}
}
menu.style.display = state.menuVisible ? 'block' : 'none';
if (fpsDisplay) fpsDisplay.style.display = state.features.fpsDisplay ? 'block' : 'none'; // Check fpsDisplay existence
if (circleVisual) circleVisual.style.border = `2px dashed ${hexToRgba(state.menuColor, 0.7)}`; // Check existence
function snakeTrailAnimationLoop() {
requestAnimationFrame(snakeTrailAnimationLoop);
drawSnakeTrail();
}
setInterval(() => {
if (!state.features.snakeTrail) {
state.snakeTrailPoints = [];
return;
}
// Get mouse screen position
const mouseX = realMouseX;
const mouseY = realMouseY;
// Convert screen position to world (game) coordinates
const viewX = window.snake ? window.snake.xx || 0 : 0;
const viewY = window.snake ? window.snake.yy || 0 : 0;
const viewZoom = window.gsc || 1;
const screenCenterX = window.innerWidth / 2;
const screenCenterY = window.innerHeight / 2;
// This formula converts screen (mouse) to world coordinates
const worldX = viewX + (mouseX - screenCenterX) / viewZoom;
const worldY = viewY + (mouseY - screenCenterY) / viewZoom;
if (
state.snakeTrailPoints.length === 0 ||
Math.abs(state.snakeTrailPoints[state.snakeTrailPoints.length-1].x - worldX) > 1 ||
Math.abs(state.snakeTrailPoints[state.snakeTrailPoints.length-1].y - worldY) > 1
) {
state.snakeTrailPoints.push({
x: worldX,
y: worldY,
time: Date.now()
});
// Limit trail length
if (state.snakeTrailPoints.length > 100) state.snakeTrailPoints.shift();
}
}, 30);
// --- START: PARTICLE ENGINE ---
function startParticleAnimation(theme, canvas) {
if (!canvas) return;
const ctx = canvas.getContext('2d');
let particles = [];
let animationFrameId;
// This function makes sure the particle area is the same size as the profile card
function resizeCanvas() {
const parent = canvas.parentElement;
if (parent) {
canvas.width = parent.offsetWidth;
canvas.height = parent.offsetHeight;
}
}
// This function creates the particles with random positions and speeds
function createParticles(config) {
particles = [];
const area = canvas.width * canvas.height;
const count = Math.min(config.maxCount, Math.floor(area / (config.density || 5000))); // Ensure not too many particles
for (let i = 0; i < count; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() * (config.vx.max - config.vx.min) + config.vx.min) * (Math.random() < 0.5 ? -1 : 1),
vy: (Math.random() * (config.vy.max - config.vy.min) + config.vy.min),
radius: Math.random() * (config.radius.max - config.radius.min) + config.radius.min,
alpha: Math.random() * (config.alpha.max - config.alpha.min) + config.alpha.min,
alphaChange: config.alphaChange || 0,
color: config.colors[Math.floor(Math.random() * config.colors.length)]
});
}
}
// The main animation loop that draws everything
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach((p, index) => {
p.x += p.vx;
p.y += p.vy;
p.alpha -= p.alphaChange;
// Reset particle if it goes off-screen or fades out
if (p.y > canvas.height || p.y < 0 || p.x < 0 || p.x > canvas.width || p.alpha <= 0) {
particles.splice(index, 1); // Remove old particle
// Add a new one to replace it
const config = themeConfigs[theme];
particles.push({
x: Math.random() * canvas.width,
y: canvas.height, // Start from the bottom for rising effects
vx: (Math.random() * (config.vx.max - config.vx.min) + config.vx.min) * (Math.random() < 0.5 ? -1 : 1),
vy: (Math.random() * (config.vy.max - config.vy.min) + config.vy.min),
radius: Math.random() * (config.radius.max - config.radius.min) + config.radius.min,
alpha: Math.random() * (config.alpha.max - config.alpha.min) + config.alpha.min,
alphaChange: config.alphaChange || 0,
color: config.colors[Math.floor(Math.random() * config.colors.length)]
});
}
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${p.color}, ${p.alpha})`;
ctx.fill();
});
animationFrameId = requestAnimationFrame(animate);
}
// --- Particle Configurations for Each Theme ---
const themeConfigs = {
fire: {
maxCount: 70, density: 4000,
vx: { min: -0.3, max: 0.3 }, vy: { min: -0.8, max: -0.2 }, // Moves upwards
radius: { min: 1, max: 4 }, alpha: { min: 0.4, max: 0.9 },
alphaChange: 0.005, // Fades out
colors: ['255,87,34', '255,152,0', '244,67,54']
},
ocean: {
maxCount: 50, density: 6000,
vx: { min: -0.2, max: 0.2 }, vy: { min: -0.6, max: -0.1 }, // Moves upwards slowly
radius: { min: 1, max: 5 }, alpha: { min: 0.1, max: 0.5 },
alphaChange: 0.002,
colors: ['255,255,255', '173,216,230']
},
forest: {
maxCount: 100, density: 3000,
vx: { min: -0.1, max: 0.1 }, vy: { min: -0.4, max: -0.05 }, // Barely moves
radius: { min: 0.5, max: 2 }, alpha: { min: 0.3, max: 0.8 },
alphaChange: 0.001,
colors: ['200,255,200', '255,255,255'] // Spores/fireflies
},
matrix: {
maxCount: 150, density: 2000,
vx: { min: 0, max: 0 }, vy: { min: 0.5, max: 1.5 }, // Moves downwards only
radius: { min: 2, max: 4 }, alpha: { min: 0.8, max: 1.0 },
alphaChange: 0.01,
colors: ['0,255,0']
}
};
if (themeConfigs[theme]) {
resizeCanvas();
createParticles(themeConfigs[theme]);
// Small delay to ensure canvas is ready
setTimeout(() => {
if (animationFrameId) cancelAnimationFrame(animationFrameId); // Clear any previous animation
animate();
}, 10);
}
return () => {
cancelAnimationFrame(animationFrameId);
};
}
// --- END: PARTICLE ENGINE ---
// --- START: FINAL GAME SUITE LOGIC (with Blackjack) ---
function showGameMenu() {
const gameModal = document.getElementById('game-modal-overlay');
if (!gameModal) return;
gameModal.style.display = 'flex';
const repDisplay = document.getElementById('player-rep-display');
const user = firebase.auth().currentUser;
if(user) {
const userRepRef = firebase.database().ref(`playerData/${user.uid}/rep`);
userRepRef.on('value', (snapshot) => {
const playerRep = snapshot.val() || 0;
repDisplay.textContent = `Your REP: ${playerRep.toLocaleString()}`;
});
} else {
repDisplay.textContent = 'Log in to play!';
}
const tabButtons = gameModal.querySelectorAll('.game-tab-btn');
const gameContainers = gameModal.querySelectorAll('.game-container');
tabButtons.forEach(button => {
button.onclick = () => {
tabButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
gameContainers.forEach(container => container.style.display = 'none');
document.getElementById('game-' + button.dataset.gametab).style.display = 'flex';
if(window.spinTimerInterval) clearInterval(window.spinTimerInterval);
if (button.dataset.gametab === 'spinner') startSpinnerGame();
if (button.dataset.gametab === 'slots') startSlotMachineGame();
if (button.dataset.gametab === 'roulette') startRouletteGame();
if (button.dataset.gametab === 'blackjack') startBlackjackGame();
};
});
gameModal.onclick = (e) => {
if (e.target.id === 'game-modal-overlay') {
gameModal.style.display = 'none';
if (window.spinTimerInterval) clearInterval(window.spinTimerInterval);
}
};
// Reset to default tab on open
tabButtons.forEach(btn => btn.classList.remove('active'));
gameContainers.forEach(c => c.style.display = 'none');
tabButtons[0].classList.add('active');
gameContainers[0].style.display = 'flex';
startSpinnerGame();
}
function startSpinnerGame() {
const spinButton = document.getElementById('spin-button');
const spinnerDisplay = document.getElementById('spinner-display');
const spinnerResult = document.getElementById('spinner-result');
const spinTimer = document.getElementById('spin-timer');
const COOLDOWN_HOURS = 24;
let lastSpin = parseInt(localStorage.getItem('lastRepSpinTime') || '0', 10);
const cooldownMillis = COOLDOWN_HOURS * 3600000;
function updateTimer() {
const remainingTime = (lastSpin + cooldownMillis) - Date.now();
if (remainingTime <= 0) {
spinTimer.textContent = 'You can spin now!';
spinButton.disabled = false; spinButton.style.opacity = '1';
if (window.spinTimerInterval) clearInterval(window.spinTimerInterval);
return;
}
const h = Math.floor(remainingTime / 3600000);
const m = Math.floor((remainingTime % 3600000) / 60000);
const s = Math.floor((remainingTime % 60000) / 1000);
spinTimer.textContent = `Next spin in: ${h}h ${m}m ${s}s`;
spinButton.disabled = true; spinButton.style.opacity = '0.5';
}
updateTimer();
if (Date.now() - lastSpin < cooldownMillis) {
window.spinTimerInterval = setInterval(updateTimer, 1000);
}
spinButton.onclick = () => {
if (Date.now() - (parseInt(localStorage.getItem('lastRepSpinTime') || '0', 10)) < cooldownMillis) return;
spinButton.disabled = true;
spinnerResult.textContent = '';
const prizes = [1, 5, 5, 10, 10, 10, 25, 25, 50, 100];
const prize = prizes[Math.floor(Math.random() * prizes.length)];
let spinAnim = setInterval(() => spinnerDisplay.textContent = Math.floor(Math.random() * 100) + 1, 50);
setTimeout(() => {
clearInterval(spinAnim);
spinnerDisplay.textContent = prize;
spinnerResult.textContent = `You won ${prize} REP!`;
lastSpin = Date.now();
localStorage.setItem('lastRepSpinTime', lastSpin.toString());
const user = firebase.auth().currentUser;
if (user) { firebase.database().ref(`playerData/${user.uid}/rep`).transaction(rep => (rep || 0) + prize); }
updateTimer();
window.spinTimerInterval = setInterval(updateTimer, 1000);
}, 2000);
};
}
function startSlotMachineGame() {
const user = firebase.auth().currentUser;
if (!user) return;
const spinBtn = document.getElementById('slot-spin-btn');
const reels = document.querySelectorAll('.slot-reel');
const resultDisplay = document.getElementById('slot-result');
const betAmountInput = document.getElementById('slot-bet-amount');
const userRepRef = firebase.database().ref(`playerData/${user.uid}/rep`);
const BASE_BET = 5; // The original bet amount for payout scaling
const SYMBOLS = ['🍋', '🍒', '🍊', '🍉', '💎', '7️⃣'];
// Payouts are now per-symbol for a 3-in-a-row line
const PAYOUTS = { '🍋': 10, '🍒': 15, '🍊': 20, '🍉': 25, '💎': 50, '7️⃣': 100 };
// Define the 8 winning paylines by their grid indices (0-8)
const paylines = [
[0, 1, 2], // Top row
[3, 4, 5], // Middle row
[6, 7, 8], // Bottom row
[0, 3, 6], // Left column
[1, 4, 7], // Middle column
[2, 5, 8], // Right column
[0, 4, 8], // Diagonal top-left to bottom-right
[2, 4, 6] // Diagonal top-right to bottom-left
];
let isSpinning = false;
// Function to check all paylines for wins
function checkWin(finalReels) {
let totalPayout = 0;
const winningLineIndices = new Set();
paylines.forEach(line => {
const [a, b, c] = line; // Get the indices for this line
// Check if the symbols at these indices are the same
if (finalReels[a] === finalReels[b] && finalReels[b] === finalReels[c]) {
const winningSymbol = finalReels[a];
totalPayout += PAYOUTS[winningSymbol];
// Add the indices of the winning line to a set to highlight them
line.forEach(index => winningLineIndices.add(index));
}
});
return { totalPayout, winningLineIndices };
}
spinBtn.onclick = async () => {
if (isSpinning) return;
const betAmount = parseInt(betAmountInput.value);
if (isNaN(betAmount) || betAmount < 1) {
resultDisplay.textContent = "Invalid bet amount!";
return;
}
const snapshot = await userRepRef.once('value');
if (snapshot.val() < betAmount) {
resultDisplay.textContent = "Not enough REP to play!";
return;
}
isSpinning = true;
spinBtn.disabled = true;
spinBtn.style.opacity = '0.5';
resultDisplay.textContent = '';
reels.forEach(reel => reel.classList.remove('win-line')); // Clear previous win highlights
await userRepRef.transaction(rep => (rep || 0) - betAmount);
let spinCount = 0;
const spinInterval = setInterval(() => {
reels.forEach(reel => reel.textContent = SYMBOLS[Math.floor(Math.random() * SYMBOLS.length)]);
spinCount++;
if (spinCount > 20) {
clearInterval(spinInterval);
finishSpin(betAmount);
}
}, 100);
};
function finishSpin(betAmount) {
// Generate the final state for all 9 reels
const finalReels = Array.from({ length: 9 }, () => SYMBOLS[Math.floor(Math.random() * SYMBOLS.length)]);
// Update the UI with the final symbols
reels.forEach((reel, i) => reel.textContent = finalReels[i]);
const { totalPayout, winningLineIndices } = checkWin(finalReels);
if (totalPayout > 0) {
const payoutMultiplier = betAmount / BASE_BET;
const finalWinnings = Math.floor(totalPayout * payoutMultiplier);
resultDisplay.textContent = `WINNER! You won ${finalWinnings.toLocaleString()} REP!`;
userRepRef.transaction(rep => (rep || 0) + finalWinnings);
// FIX: Grab name directly from local storage
const currentName = localStorage.getItem("nickname") || "Anon";
firebase.database().ref(`minigameHighscores/slots/${user.uid}`).transaction(currentData => {
const currentScore = (currentData && currentData.score) ? currentData.score : 0;
if (finalWinnings > currentScore) {
// Save BOTH name and score
return { name: currentName, score: finalWinnings };
}
return currentData;
});
// --- END NEW ---
} else {
resultDisplay.textContent = "Try again!";
}
isSpinning = false;
spinBtn.disabled = false;
spinBtn.style.opacity = '1';
}
}
function startRouletteGame() {
const user = firebase.auth().currentUser; if (!user) return;
const wheel = document.getElementById('roulette-wheel');
const resultDisplay = document.getElementById('roulette-result');
const betAmountInput = document.getElementById('roulette-bet-amount');
const betButtons = document.querySelectorAll('.roulette-bet-btn');
const userRepRef = firebase.database().ref(`playerData/${user.uid}/rep`);
let isSpinning = false, selectedBet = null, currentRotation = 0;
const numbers = [0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26];
const numberColors = {};
[0].forEach(n => numberColors[n] = 'green');
[1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36].forEach(n => numberColors[n] = 'red');
[2,4,6,8,10,11,13,15,17,20,22,24,26,28,29,31,33,35].forEach(n => numberColors[n] = 'black');
const spinWheel = async () => {
if (isSpinning || !selectedBet) { resultDisplay.textContent = "Select a color to bet on!"; return; }
const betAmount = parseInt(betAmountInput.value);
if (isNaN(betAmount) || betAmount <= 0) { resultDisplay.textContent = "Invalid bet amount!"; return; }
const snapshot = await userRepRef.once('value');
if (snapshot.val() < betAmount) { resultDisplay.textContent = "Not enough REP!"; return; }
isSpinning = true;
betButtons.forEach(b => { b.disabled = true; b.style.opacity = 0.5; });
resultDisplay.textContent = 'Spinning...';
await userRepRef.transaction(rep => (rep || 0) - betAmount);
const randomIndex = Math.floor(Math.random() * numbers.length);
const winningNumber = numbers[randomIndex];
const anglePerNumber = 360 / numbers.length;
const randomOffset = (Math.random() - 0.5) * anglePerNumber * 0.8;
const targetRotation = (360 * 5) + (anglePerNumber * (numbers.length - 1 - randomIndex)) + randomOffset;
currentRotation += targetRotation;
wheel.style.transform = `rotate(${currentRotation}deg)`;
setTimeout(() => {
const winningColor = numberColors[winningNumber];
resultDisplay.innerHTML = `Landed on ${winningNumber}!`;
const payout = (selectedBet === winningColor) ? betAmount * (winningColor === 'green' ? 14 : 2) : 0;
if (payout > 0) {
resultDisplay.innerHTML += ` You won ${payout.toLocaleString()} REP!`;
userRepRef.transaction(rep => (rep || 0) + payout);
const currentName = localStorage.getItem("nickname") || "Anon";
firebase.database().ref(`minigameHighscores/roulette/${user.uid}`).transaction(currentData => {
const currentScore = (currentData && currentData.score) ? currentData.score : 0;
if (payout > currentScore) {
return { name: currentName, score: payout };
}
return currentData;
});
} else {
resultDisplay.innerHTML += ` You lost your bet.`;
}
isSpinning = false;
betButtons.forEach(b => { b.disabled = false; b.style.opacity = 1; });
selectedBet = null;
betButtons.forEach(btn => btn.classList.remove('selected'));
}, 4100);
};
betButtons.forEach(button => button.onclick = () => {
if (isSpinning) return;
betButtons.forEach(btn => btn.classList.remove('selected'));
button.classList.add('selected');
selectedBet = button.dataset.bet;
spinWheel();
});
}
function startBlackjackGame() {
const user = firebase.auth().currentUser; if (!user) return;
const userRepRef = firebase.database().ref(`playerData/${user.uid}/rep`);
const dealBtn = document.getElementById('blackjack-deal-btn'), hitBtn = document.getElementById('blackjack-hit-btn'), standBtn = document.getElementById('blackjack-stand-btn'),
betControls = document.getElementById('blackjack-bet-controls'), actionControls = document.getElementById('blackjack-action-controls'),
betInput = document.getElementById('blackjack-bet-amount'), resultEl = document.getElementById('blackjack-result');
let deck = [], playerHand = [], dealerHand = [], bet = 0;
const createDeck = () => {
deck = []; const suits = ['♥', '♦', '♣', '♠'], values = ['2','3','4','5','6','7','8','9','10','J','Q','K','A'];
for(let suit of suits) { for(let value of values) { deck.push({suit, value}); } }
};
const shuffleDeck = () => { for (let i = deck.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [deck[i], deck[j]] = [deck[j], deck[i]]; } };
const getCardValue = (card) => { if (['J', 'Q', 'K'].includes(card.value)) return 10; if (card.value === 'A') return 11; return parseInt(card.value); };
const getHandScore = (hand) => {
let score = hand.reduce((sum, card) => sum + getCardValue(card), 0);
let aceCount = hand.filter(card => card.value === 'A').length;
while(score > 21 && aceCount > 0) { score -= 10; aceCount--; }
return score;
};
const renderHand = (hand, elementId, scoreId, hideFirstCard = false) => {
const handEl = document.getElementById(elementId); handEl.innerHTML = '';
hand.forEach((card, index) => {
const cardEl = document.createElement('div'); cardEl.className = 'blackjack-card';
if (['♥', '♦'].includes(card.suit)) cardEl.classList.add('red');
if (hideFirstCard && index === 0) { cardEl.classList.add('blackjack-card-hidden'); } else { cardEl.innerHTML = `${card.value}${card.suit}${card.suit}${card.value}${card.suit}`; }
handEl.appendChild(cardEl);
});
document.getElementById(scoreId).textContent = hideFirstCard ? getCardValue(hand[1]) : getHandScore(hand);
};
const endRound = (message, payoutMultiplier) => {
renderHand(dealerHand, 'blackjack-dealer-cards', 'blackjack-dealer-score', false);
resultEl.textContent = message;
if (payoutMultiplier > 0) {
userRepRef.transaction(rep => (rep || 0) + Math.floor(bet * payoutMultiplier));
// Only count actual wins (multiplier > 1), ignore ties/pushes (multiplier == 1)
if (payoutMultiplier > 1) {
// --- UPDATED SAVE LOGIC ---
const userName = localStorage.getItem("nickname") || "Anon";
firebase.database().ref(`minigameHighscores/blackjack/${user.uid}`).transaction(currentData => {
const currentWins = (currentData && currentData.score) ? currentData.score : 0;
// Increment total wins by 1
return { name: userName, score: currentWins + 1 };
});
}
// --- END NEW ---
}
betControls.style.display = 'flex'; actionControls.style.display = 'none';
};
dealBtn.onclick = async () => {
bet = parseInt(betInput.value);
if (isNaN(bet) || bet <= 0) { resultEl.textContent = "Invalid bet!"; return; }
const snapshot = await userRepRef.once('value');
if (snapshot.val() < bet) { resultEl.textContent = "Not enough REP!"; return; }
await userRepRef.transaction(rep => (rep || 0) - bet);
createDeck(); shuffleDeck(); playerHand = [deck.pop(), deck.pop()]; dealerHand = [deck.pop(), deck.pop()];
resultEl.textContent = ''; betControls.style.display = 'none'; actionControls.style.display = 'flex';
renderHand(playerHand, 'blackjack-player-cards', 'blackjack-player-score');
renderHand(dealerHand, 'blackjack-dealer-cards', 'blackjack-dealer-score', true);
if (getHandScore(playerHand) === 21) {
if (getHandScore(dealerHand) === 21) { endRound("Push! It's a tie.", 1); }
else { endRound("Blackjack! You win!", 2.5); }
}
};
hitBtn.onclick = () => {
playerHand.push(deck.pop()); renderHand(playerHand, 'blackjack-player-cards', 'blackjack-player-score');
if (getHandScore(playerHand) > 21) { endRound("Bust! You lose.", 0); }
};
standBtn.onclick = () => {
actionControls.style.display = 'none'; renderHand(dealerHand, 'blackjack-dealer-cards', 'blackjack-dealer-score', false);
const dealerTurn = setInterval(() => {
const dealerScore = getHandScore(dealerHand);
if (dealerScore < 17) { dealerHand.push(deck.pop()); renderHand(dealerHand, 'blackjack-dealer-cards', 'blackjack-dealer-score'); }
else {
clearInterval(dealerTurn); const playerScore = getHandScore(playerHand);
if (dealerScore > 21 || playerScore > dealerScore) { endRound("You win!", 2); }
else if (playerScore < dealerScore) { endRound("Dealer wins.", 0); }
else { endRound("Push! It's a tie.", 1); }
}
}, 800);
};
// Initial state reset
betControls.style.display = 'flex'; actionControls.style.display = 'none'; resultEl.textContent = '';
renderHand([], 'blackjack-player-cards', 'blackjack-player-score');
renderHand([], 'blackjack-dealer-cards', 'blackjack-dealer-score');
}
// --- END: FINAL GAME SUITE LOGIC ---
// --- START: FINAL GAME SUITE MODAL (with Blackjack) ---
if (!document.getElementById('game-modal-overlay')) {
const gameModalHTML = `
Your REP: Loading...
Blackjack
Dealer's Hand (0)
Your Hand (0)
`;
const tempDiv = document.createElement('div');
tempDiv.innerHTML = gameModalHTML;
document.body.appendChild(tempDiv.firstElementChild);
// Copy original game content into the new structure
document.getElementById('game-spinner').innerHTML = `
`;
popupOverlay.appendChild(popupContent);
document.body.appendChild(popupOverlay);
// Make it visible
popupOverlay.style.display = 'flex';
// Add the event listener for the OK button
document.getElementById('info-popup-ok-btn').addEventListener('click', () => {
popupOverlay.style.display = 'none';
localStorage.setItem(popupVersion, 'true'); // Mark as seen
popupOverlay.remove(); // Clean up the element from the page
});
}
})();
// --- END: One-Time Informational Popup ---
})(); // End of the main IIFE