Reset dirty cash, lab equipment, and street dealers in exchange for permanent Clean Offshore Bonds.
Each Bond permanently boosts production (+3%) and sale values (+4%).
CLEAN BONDS UPON LAUNDERING:
+${nextBonds} Bonds
Career Volume: $${Math.floor(G.totalEarned).toLocaleString()}
GLOBAL SYNDICATE ACHIEVEMENTS
`;
ACHIEVEMENTS.forEach(a => {
const has = G.achievements[a.id];
html += `
${has ? '🏆 ' : '🔒 '}${a.name}
${a.desc}
${a.reward}
`;
});
dom.viewport.innerHTML = html;
}
// --- CORE ACTIONS ---
function buyRaw(drugId) {
const d = DRUG_CATALOG.find(x => x.id === drugId);
const cost = Math.floor(d.rawCost * getRawCostMultiplier());
if (G.cash >= cost) {
G.cash -= cost;
G.drugs[drugId].raw += 1;
updateHud();
renderViewport();
}
}
function manufacture(drugId) {
const data = G.drugs[drugId];
if (data.raw >= 1) {
data.raw -= 1;
let yieldCount = 1;
// 99.1% crystallization perk
if (G.skills['chem_3'] && Math.random() < 0.10) {
yieldCount = 3;
showToast('99.1% Purity Proc! 3x Yield!', false);
}
data.product += yieldCount;
addHeat(0.5);
updateHud();
renderViewport();
}
}
function sellManual(drugId, amount = 1) {
const d = DRUG_CATALOG.find(x => x.id === drugId);
const data = G.drugs[drugId];
const actual = Math.min(amount, data.product);
if (actual > 0) {
data.product -= actual;
const profit = actual * Math.floor(d.sellPrice * getSellPriceMultiplier());
G.cash += profit;
G.totalEarned += profit;
addHeat(actual * 0.8);
checkAchievements();
updateHud();
renderViewport();
}
}
function hireWorker(drugId) {
const cost = getWorkerCost(drugId);
if (G.cash >= cost) {
G.cash -= cost;
G.drugs[drugId].workers += 1;
checkAchievements();
updateHud();
renderViewport();
}
}
function hireDealer(drugId) {
const cost = getDealerCost(drugId);
if (G.cash >= cost) {
G.cash -= cost;
G.drugs[drugId].dealers += 1;
updateHud();
renderViewport();
}
}
function unlockDrug(drugId) {
const cost = getUnlockCost(drugId);
if (G.cash >= cost && !G.drugs[drugId].unlocked) {
G.cash -= cost;
G.drugs[drugId].unlocked = true;
showToast(`Unlocked formula for ${DRUG_CATALOG.find(d => d.id === drugId).name}!`);
checkAchievements();
updateHud();
renderViewport();
}
}
function blendRecipe(recipeId) {
const r = SYNTHESIS_RECIPES.find(x => x.id === recipeId);
const canCook = Object.entries(r.ingredients).every(([drugId, amt]) => G.drugs[drugId].product >= amt);
if (canCook) {
Object.entries(r.ingredients).forEach(([drugId, amt]) => {
G.drugs[drugId].product -= amt;
});
G.cash += r.sellPrice;
G.totalEarned += r.sellPrice;
addHeat(5);
showToast(`Synthesized & Sold ${r.name} for +$${r.sellPrice.toLocaleString()}!`);
updateHud();
renderViewport();
}
}
function buySkill(skillId) {
const s = SKILL_TREE.find(x => x.id === skillId);
if (G.cash >= s.cost && !G.skills[skillId]) {
G.cash -= s.cost;
G.skills[skillId] = true;
showToast(`Skill Activated: ${s.name}`);
updateHud();
renderViewport();
}
}
function launderMoney() {
const bondsToEarn = Math.floor(Math.sqrt(Math.max(0, G.totalEarned) / 25000));
const gain = Math.max(0, bondsToEarn - G.cleanBonds);
if (gain < 1) return;
if (confirm(`Launder all assets for +${gain} Clean Offshore Bonds? Your syndicate operations will reset.`)) {
G.cleanBonds += gain;
G.cash = 50.00;
G.heat = 0;
DRUG_CATALOG.forEach(d => {
G.drugs[d.id] = {
unlocked: d.unlockedDefault,
raw: 0,
product: 0,
workers: 0,
dealers: 0
};
});
checkAchievements();
showToast(`Laundered! You now have ${G.cleanBonds} Clean Bonds.`);
updateHud();
renderViewport();
}
}
// --- POLICE HEAT & RAIDS ---
function addHeat(amount) {
let riseMod = G.skills['smug_1'] ? 0.65 : 1.0;
G.heat = Math.min(100, G.heat + (amount * riseMod));
}
function checkRaidEvent() {
if (G.heat >= 85 && Math.random() < 0.25) {
triggerRaidModal();
}
}
function triggerRaidModal() {
const bribeCost = Math.max(100, Math.floor(G.cash * 0.15 * (G.skills['smug_2'] ? 0.5 : 1.0)));
dom.raidBribeCost.textContent = bribeCost.toLocaleString();
dom.raidModal.classList.add('active');
document.getElementById('btn-raid-bribe').onclick = () => {
if (G.cash >= bribeCost) {
G.cash -= bribeCost;
G.heat = 0;
dom.raidModal.classList.remove('active');
showToast('Sheriff bribed. Heat cleared.');
updateHud();
} else {
showToast('Insufficient cash for bribe!', true);
}
};
document.getElementById('btn-raid-stash').onclick = () => {
G.heat = 20;
dom.raidModal.classList.remove('active');
showToast('Stash flushed & secured. Heat diminished.');
updateHud();
};
document.getElementById('btn-raid-shootout').onclick = () => {
dom.raidModal.classList.remove('active');
if (Math.random() < 0.5) {
const reward = Math.floor(G.cash * 0.5);
G.cash += reward;
G.heat = 0;
showToast(`Broke through blockade! Seized +$${reward.toLocaleString()}!`);
} else {
// Seize 30% of inventory
DRUG_CATALOG.forEach(d => {
G.drugs[d.id].product = Math.floor(G.drugs[d.id].product * 0.7);
});
G.heat = 30;
showToast('Agents seized 30% of stored product!', true);
}
updateHud();
renderViewport();
};
}
// --- ACHIEVEMENTS CHECK ---
function checkAchievements() {
if (G.totalEarned > 0 && !G.achievements['first_hit']) {
G.achievements['first_hit'] = true;
showToast('Achievement: First Batch!');
}
if (G.drugs['weed'].workers >= 10 && !G.achievements['grower_op']) {
G.achievements['grower_op'] = true;
showToast('Achievement: Green Thumb!');
}
if (G.drugs['meth'].unlocked && !G.achievements['blue_sky']) {
G.achievements['blue_sky'] = true;
showToast('Achievement: Say My Name!');
}
if (G.drugs['cocaine'].unlocked && !G.achievements['cartel_tier']) {
G.achievements['cartel_tier'] = true;
showToast('Achievement: El Patrón!');
}
if (G.cash >= 1000000 && !G.achievements['millionaire']) {
G.achievements['millionaire'] = true;
showToast('Achievement: Seven Figures!');
}
if (G.cleanBonds >= 1 && !G.achievements['laundered']) {
G.achievements['laundered'] = true;
showToast('Achievement: Clean Hands!');
}
}
// --- TICK & GAME LOOP ---
function tick(deltaSec) {
let earnedThisTick = 0;
const prodMulti = getProductionMultiplier();
const sellMulti = getSellPriceMultiplier();
const dealerSpeed = getDealerSpeedMultiplier();
DRUG_CATALOG.forEach(d => {
const data = G.drugs[d.id];
if (!data.unlocked) return;
// Auto Production
if (data.workers > 0 && data.raw > 0) {
const prodPerSec = data.workers * d.produceRate * prodMulti;
const toMake = Math.min(data.raw, prodPerSec * deltaSec);
data.raw -= toMake;
data.product += toMake;
}
// Auto Sales
if (data.dealers > 0 && data.product > 0) {
const sellPerSec = data.dealers * d.sellRate * dealerSpeed;
const toSell = Math.min(data.product, sellPerSec * deltaSec);
data.product -= toSell;
const revenue = toSell * Math.floor(d.sellPrice * sellMulti);
G.cash += revenue;
G.totalEarned += revenue;
earnedThisTick += revenue;
}
});
// Heat decay
if (G.heat > 0) {
G.heat = Math.max(0, G.heat - (0.5 * deltaSec));
}
if (deltaSec > 0) {
cashRateTracker = cashRateTracker * 0.85 + (earnedThisTick / deltaSec) * 0.15;
}
// Periodic raid check
if (Date.now() - lastRaidCheck > 12000) {
lastRaidCheck = Date.now();
checkRaidEvent();
}
updateHud();
}
function updateHud() {
dom.cash.textContent = G.cash.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
dom.rate.textContent = cashRateTracker.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
dom.bonds.textContent = G.cleanBonds;
dom.multiplier.textContent = Math.round((G.cleanBonds * 4));
dom.heatPct.textContent = `${Math.round(G.heat)}%`;
dom.heatBar.style.width = `${Math.min(100, Math.round(G.heat))}%`;
}
// Game loop with requestAnimationFrame
let lastFrameTime = performance.now();
function gameLoop(now) {
const delta = (now - lastFrameTime) / 1000;
lastFrameTime = now;
if (delta > 0 && delta < 5) {
tick(delta);
}
requestAnimationFrame(gameLoop);
}
// Clock updates
setInterval(() => {
const d = new Date();
dom.clock.textContent = d.toTimeString().substring(0, 5);
}, 1000);
// Auto-save every 5 seconds (Local & Cloud)
setInterval(async () => {
localStorage.setItem('el_puro_local_save', JSON.stringify(G));
if (API.getToken()) {
await API.saveRemote(G);
}
}, 5000);
// --- INITIALIZATION & EVENTS ---
document.addEventListener('DOMContentLoaded', async () => {
// Check auth status
const username = API.getUsername();
if (username) {
dom.userBadge.textContent = `[${username}]`;
}
// Load Save: Try cloud first if logged in, fallback to local
let loaded = false;
if (API.getToken()) {
const remote = await API.loadRemoteSave();
if (remote) {
G = { ...INITIAL_GAME_STATE, ...remote };
loaded = true;
}
}
if (!loaded) {
const local = localStorage.getItem('el_puro_local_save');
if (local) {
try {
G = { ...INITIAL_GAME_STATE, ...JSON.parse(local) };
} catch (e) {
console.warn('Corrupt local save, initializing new game');
}
}
}
// Setup navigation tabs
document.querySelectorAll('.nav-tab').forEach(b => {
b.addEventListener('click', () => setTab(b.dataset.tab));
});
// Auth Modal handling
dom.userBadge.addEventListener('click', () => dom.authModal.classList.add('active'));
document.getElementById('btn-close-auth').addEventListener('click', () => dom.authModal.classList.remove('active'));
document.getElementById('btn-login-submit').addEventListener('click', async () => {
const u = document.getElementById('auth-username').value.trim();
const p = document.getElementById('auth-password').value;
if (!u || !p) return showToast('Enter username and password', true);
try {
const res = await API.login(u, p);
dom.userBadge.textContent = `[${res.username}]`;
dom.authModal.classList.remove('active');
showToast(`Connected: Welcome ${res.username}`);
// Sync game save from cloud
const remote = await API.loadRemoteSave();
if (remote) {
G = { ...INITIAL_GAME_STATE, ...remote };
renderViewport();
}
} catch (e) {
showToast(e.message, true);
}
});
document.getElementById('btn-register-submit').addEventListener('click', async () => {
const u = document.getElementById('auth-username').value.trim();
const p = document.getElementById('auth-password').value;
const hp = document.getElementById('auth-website-hp').value; // Honeypot field!
if (!u || !p) return showToast('Enter username and password', true);
try {
const res = await API.register(u, p, hp);
dom.userBadge.textContent = `[${res.username}]`;
dom.authModal.classList.remove('active');
showToast(`Syndicate registered: ${res.username}`);
await API.saveRemote(G);
} catch (e) {
showToast(e.message, true);
}
});
renderViewport();
updateHud();
requestAnimationFrame(gameLoop);
});