🎮ArcadeLab

Football Empire — Build Your Dynasty

by LunarGalaxy87
625 lines23.1 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Football Empire — Build Your Dynasty</title>
<style>
  :root{
    --pitch-dark: #0b2818;
    --pitch: #123a22;
    --pitch-light: #1c5230;
    --line: #ffffff;
    --gold: #d4af37;
    --gold-bright: #f4d968;
    --ink: #eef7ee;
    --ink-dim: #9db8a3;
    --danger: #c0453a;
  }
  *{box-sizing:border-box; -webkit-tap-highlight-color:transparent;}
  html,body{margin:0;padding:0;height:100%;overflow:hidden;background:var(--pitch-dark);}
  body{
    font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
    color:var(--ink);
    background:
      repeating-linear-gradient(90deg, rgba(255,255,255,0.02) 0 2px, transparent 2px 40px),
      linear-gradient(180deg, var(--pitch) 0%, var(--pitch-dark) 100%);
    display:flex; flex-direction:column; height:100vh; height:100dvh;
    user-select:none;
  }

  header{
    padding:14px 16px 10px;
    display:flex; flex-direction:column; gap:8px;
    border-bottom:2px solid rgba(255,255,255,0.08);
    background:rgba(0,0,0,0.15);
  }
  .titlebar{display:flex; justify-content:space-between; align-items:baseline;}
  .club-name{
    font-family: Georgia, 'Times New Roman', serif;
    font-weight:700; font-size:20px; letter-spacing:0.5px;
    color:var(--gold-bright);
  }
  .league{ font-size:11px; color:var(--ink-dim); text-transform:uppercase; letter-spacing:1.5px;}

  .stats-row{ display:flex; gap:10px; }
  .stat{
    flex:1; background:rgba(255,255,255,0.05); border:1px solid rgba(255,255,255,0.08);
    border-radius:10px; padding:8px 10px;
  }
  .stat-label{ font-size:10px; color:var(--ink-dim); text-transform:uppercase; letter-spacing:1px;}
  .stat-value{ font-size:17px; font-weight:700; color:var(--ink); font-variant-numeric: tabular-nums;}
  .stat-value.gold{ color:var(--gold-bright); }

  .prestige-bar-wrap{ margin-top:2px;}
  .prestige-bar-track{
    height:6px; border-radius:4px; background:rgba(255,255,255,0.08); overflow:hidden;
  }
  .prestige-bar-fill{
    height:100%; background:linear-gradient(90deg, var(--gold), var(--gold-bright));
    transition:width 0.3s ease; border-radius:4px;
  }
  .prestige-label{ font-size:10px; color:var(--ink-dim); margin-top:4px; display:flex; justify-content:space-between;}

  main{
    flex:1; overflow-y:auto; -webkit-overflow-scrolling:touch;
    padding:14px 16px 100px;
    display:flex; flex-direction:column; gap:14px;
  }

  .pitch-zone{
    position:relative;
    height:150px;
    border-radius:14px;
    background:
      repeating-linear-gradient(90deg, var(--pitch-light) 0 34px, var(--pitch) 34px 68px);
    border:3px solid rgba(255,255,255,0.25);
    overflow:hidden;
    display:flex; align-items:center; justify-content:center;
    flex-shrink:0;
  }
  .pitch-zone::before{
    content:''; position:absolute; inset:8px; border:2px solid rgba(255,255,255,0.3); border-radius:8px;
  }
  .pitch-zone::after{
    content:''; position:absolute; top:50%; left:50%; width:44px; height:44px;
    border:2px solid rgba(255,255,255,0.3); border-radius:50%; transform:translate(-50%,-50%);
  }
  #ball{
    width:56px; height:56px; border-radius:50%; cursor:pointer;
    background: radial-gradient(circle at 32% 32%, #fff, #e8e8e8 45%, #ccc 100%);
    box-shadow:0 6px 14px rgba(0,0,0,0.45), inset 0 -6px 10px rgba(0,0,0,0.15);
    display:flex; align-items:center; justify-content:center;
    font-size:24px;
    transition:transform 0.08s ease;
    position:relative; z-index:2;
    touch-action: manipulation;
  }
  #ball:active{ transform:scale(0.88) rotate(20deg); }
  #ball.kicked{ animation:kick 0.35s ease; }
  @keyframes kick{
    0%{ transform:scale(1); }
    30%{ transform:scale(0.8) rotate(-15deg); }
    100%{ transform:scale(1) rotate(0); }
  }
  .float-coin{
    position:absolute; font-weight:700; font-size:14px; color:var(--gold-bright);
    pointer-events:none; z-index:3;
    animation: floatUp 0.9s ease-out forwards;
    text-shadow:0 1px 3px rgba(0,0,0,0.6);
  }
  @keyframes floatUp{
    0%{ opacity:1; transform:translateY(0) scale(1); }
    100%{ opacity:0; transform:translateY(-60px) scale(1.2); }
  }
  .tap-hint{
    position:absolute; bottom:8px; left:50%; transform:translateX(-50%);
    font-size:10px; color:rgba(255,255,255,0.55); letter-spacing:0.5px; text-transform:uppercase;
  }

  .section-title{
    font-size:13px; font-weight:700; color:var(--ink-dim); text-transform:uppercase; letter-spacing:1.5px;
    margin:2px 0 -4px 2px; display:flex; align-items:center; gap:6px;
  }

  .card{
    background:linear-gradient(160deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
    border:1px solid rgba(255,255,255,0.1);
    border-radius:14px;
    padding:12px 14px;
    display:flex; align-items:center; gap:12px;
    transition:transform 0.12s ease, border-color 0.12s ease;
  }
  .card:active{ transform:scale(0.98); }
  .card.affordable{ border-color:rgba(212,175,55,0.5); }
  .card.locked{ opacity:0.45; }
  .card-icon{
    width:42px; height:42px; border-radius:10px; flex-shrink:0;
    display:flex; align-items:center; justify-content:center; font-size:22px;
    background:rgba(0,0,0,0.25);
  }
  .card-body{ flex:1; min-width:0; }
  .card-name{ font-weight:700; font-size:14px; }
  .card-desc{ font-size:11px; color:var(--ink-dim); margin-top:1px; }
  .card-owned{
    font-size:10px; color:var(--gold-bright); margin-top:2px; font-weight:700;
    letter-spacing:0.5px;
  }
  .buy-btn{
    flex-shrink:0; border:none; border-radius:10px; padding:9px 12px;
    background:linear-gradient(160deg, var(--gold-bright), var(--gold));
    color:#1a1300; font-weight:800; font-size:12px; cursor:pointer;
    display:flex; flex-direction:column; align-items:center; min-width:76px;
    box-shadow:0 3px 0 #8a6d1f;
  }
  .buy-btn:active{ transform:translateY(2px); box-shadow:0 1px 0 #8a6d1f; }
  .buy-btn:disabled{
    background:rgba(255,255,255,0.08); color:var(--ink-dim); box-shadow:none; cursor:default;
  }
  .buy-btn .cost{ font-size:12px; }
  .buy-btn .sub{ font-size:8px; opacity:0.75; text-transform:uppercase; }

  .prestige-card{
    background:linear-gradient(160deg, rgba(212,175,55,0.18), rgba(212,175,55,0.04));
    border:1px solid rgba(212,175,55,0.35);
    border-radius:14px; padding:14px; margin-top:4px;
  }
  .prestige-card h3{ margin:0 0 4px; font-size:15px; color:var(--gold-bright); font-family:Georgia, serif;}
  .prestige-card p{ margin:0 0 10px; font-size:12px; color:var(--ink-dim); line-height:1.5;}
  .prestige-btn{
    width:100%; padding:12px; border:none; border-radius:10px;
    background:linear-gradient(160deg, var(--gold-bright), var(--gold));
    color:#1a1300; font-weight:800; font-size:13px; letter-spacing:0.5px; cursor:pointer;
  }
  .prestige-btn:disabled{ background:rgba(255,255,255,0.08); color:var(--ink-dim); }

  footer{
    position:fixed; bottom:0; left:0; right:0;
    display:flex; background:rgba(6,20,12,0.92); backdrop-filter:blur(8px);
    border-top:2px solid rgba(255,255,255,0.08);
    padding:6px 6px calc(6px + env(safe-area-inset-bottom));
    gap:4px; z-index:10;
  }
  .tab{
    flex:1; background:none; border:none; color:var(--ink-dim); padding:8px 4px;
    display:flex; flex-direction:column; align-items:center; gap:2px; font-size:10px;
    border-radius:10px; cursor:pointer; font-weight:600; letter-spacing:0.3px;
  }
  .tab .ic{ font-size:19px; }
  .tab.active{ color:var(--gold-bright); background:rgba(212,175,55,0.1); }

  .toast{
    position:fixed; top:14px; left:50%; transform:translateX(-50%);
    background:rgba(0,0,0,0.85); border:1px solid var(--gold); color:var(--gold-bright);
    padding:10px 18px; border-radius:10px; font-size:13px; font-weight:700;
    z-index:100; opacity:0; pointer-events:none; transition:opacity 0.3s, top 0.3s;
    text-align:center; max-width:80%;
  }
  .toast.show{ opacity:1; top:20px; }

  .hidden{ display:none !important; }

  ::-webkit-scrollbar{ width:0; }
</style>
</head>
<body>

<header>
  <div class="titlebar">
    <div class="club-name" id="clubName">FC Origin</div>
    <div class="league" id="leagueName">Sunday League</div>
  </div>
  <div class="stats-row">
    <div class="stat">
      <div class="stat-label">Club Funds</div>
      <div class="stat-value gold" id="moneyDisplay">£0</div>
    </div>
    <div class="stat">
      <div class="stat-label">Per Second</div>
      <div class="stat-value" id="ppsDisplay">£0/s</div>
    </div>
    <div class="stat">
      <div class="stat-label">Trophies</div>
      <div class="stat-value" id="trophyDisplay">0</div>
    </div>
  </div>
  <div class="prestige-bar-wrap">
    <div class="prestige-bar-track"><div class="prestige-bar-fill" id="prestigeBar" style="width:0%"></div></div>
    <div class="prestige-label"><span id="prestigeProgressLabel">Progress to promotion</span><span id="prestigePct">0%</span></div>
  </div>
</header>

<main id="mainScroll">

  <div id="tab-club" class="tab-content">
    <div class="pitch-zone">
      <div id="ball">⚽</div>
      <div class="tap-hint">Tap the ball to train</div>
    </div>

    <div class="section-title">🏟️ Youth &amp; Staff</div>
    <div id="generatorList" style="display:flex; flex-direction:column; gap:8px;"></div>
  </div>

  <div id="tab-upgrades" class="tab-content hidden">
    <div class="section-title">⭐ Upgrades</div>
    <div id="upgradeList" style="display:flex; flex-direction:column; gap:8px;"></div>
  </div>

  <div id="tab-prestige" class="tab-content hidden">
    <div class="prestige-card">
      <h3 id="prestigeTitle">Seek Promotion</h3>
      <p id="prestigeDesc">Reach £10,000 in club funds to earn promotion. Promoting resets your funds and staff, but grants permanent Trophies — each Trophy boosts all income forever.</p>
      <button class="prestige-btn" id="prestigeBtn">Not enough funds yet</button>
    </div>
    <div class="section-title" style="margin-top:16px;">🏆 Trophy Bonuses</div>
    <div class="card">
      <div class="card-icon">📈</div>
      <div class="card-body">
        <div class="card-name">Legacy Income</div>
        <div class="card-desc" id="trophyBonusDesc">Each trophy: +10% income, permanently.</div>
      </div>
    </div>
  </div>

</main>

<footer>
  <button class="tab active" data-tab="club"><span class="ic">⚽</span>Club</button>
  <button class="tab" data-tab="upgrades"><span class="ic">⭐</span>Upgrades</button>
  <button class="tab" data-tab="prestige"><span class="ic">🏆</span>Promotion</button>
</footer>

<div class="toast" id="toast"></div>

<script>
(function(){
  "use strict";

  // ---------- Config ----------
  const SAVE_KEY = "footballEmpireSave_v1";

  const LEAGUE_NAMES = [
    "Sunday League", "County League", "Regional Division", "National League",
    "Championship", "Premier League", "Champions League", "World Elite"
  ];

  const GENERATORS = [
    { id:"scout",    name:"Youth Scout",    icon:"🔍", baseCost:15,      baseRate:0.1,  desc:"Finds raw local talent" },
    { id:"coach",    name:"Academy Coach",  icon:"🎽", baseCost:100,     baseRate:1,    desc:"Trains youth prospects" },
    { id:"striker",  name:"Star Striker",   icon:"🥅", baseCost:1100,    baseRate:8,    desc:"Scores, sells shirts" },
    { id:"stadium",  name:"Stadium Upgrade",icon:"🏟️", baseCost:12000,   baseRate:47,   desc:"More seats, more gate revenue" },
    { id:"sponsor",  name:"Kit Sponsor",    icon:"💼", baseCost:130000,  baseRate:260,  desc:"Global sponsorship deal" },
    { id:"academy",  name:"Global Academy", icon:"🌍", baseCost:1400000, baseRate:1400, desc:"Worldwide scouting network" },
  ];

  const UPGRADES = [
    { id:"boots1",  name:"Better Boots",     icon:"👟", cost:500,    desc:"Scouts +100% effective", target:"scout",   mult:2 },
    { id:"tactics1",name:"Tactics Board",    icon:"📋", cost:5000,   desc:"Coaches +100% effective", target:"coach",   mult:2 },
    { id:"boots2",  name:"Pro Boots",        icon:"👟", cost:20000,  desc:"Scouts +200% effective (again)", target:"scout", mult:3 },
    { id:"jersey1", name:"Away Kit Launch",  icon:"👕", cost:60000,  desc:"Strikers +100% effective", target:"striker", mult:2 },
    { id:"lights1", name:"Floodlights",      icon:"💡", cost:250000, desc:"Stadium +100% effective", target:"stadium", mult:2 },
    { id:"global1", name:"Global Deal",      icon:"🌐", cost:900000, desc:"Sponsors +150% effective", target:"sponsor", mult:2.5 },
    { id:"clickup1",name:"Captain's Armband",icon:"🎖️", cost:2000,   desc:"Ball tap earns 3x more", target:"click", mult:3 },
    { id:"clickup2",name:"Free Kick Master", icon:"🎯", cost:45000,  desc:"Ball tap earns 5x more (again)", target:"click", mult:5 },
    { id:"allboost",name:"Champions Mentality",icon:"🔥",cost:750000,desc:"ALL income +50%", target:"all", mult:1.5 },
  ];

  // ---------- State ----------
  let state = {
    money: 0,
    trophies: 0,
    clickValue: 1,
    owned: {},       // generator id -> count
    upgrades: {},    // upgrade id -> bool bought
    lifetimeEarned: 0,
    league: 0,
  };

  GENERATORS.forEach(g => state.owned[g.id] = 0);

  // ---------- Save / Load ----------
  function save(){
    try{ localStorage.setItem(SAVE_KEY, JSON.stringify(state)); }catch(e){}
  }
  function load(){
    try{
      const raw = localStorage.getItem(SAVE_KEY);
      if(raw){
        const parsed = JSON.parse(raw);
        state = Object.assign(state, parsed);
      }
    }catch(e){}
  }

  // ---------- Helpers ----------
  function fmt(n){
    if(n < 1000) return "£" + Math.floor(n).toLocaleString();
    const units = ["", "K", "M", "B", "T", "Qa", "Qi"];
    let u = 0;
    while(n >= 1000 && u < units.length-1){ n /= 1000; u++; }
    return "£" + n.toFixed(n < 10 ? 2 : 1) + units[u];
  }

  function trophyMultiplier(){
    return 1 + state.trophies * 0.1;
  }

  function upgradeMultFor(target){
    let mult = 1;
    UPGRADES.forEach(u => {
      if(state.upgrades[u.id] && u.target === target) mult *= u.mult;
    });
    // "all" upgrades apply to everything including click
    UPGRADES.forEach(u => {
      if(state.upgrades[u.id] && u.target === "all") mult *= u.mult;
    });
    return mult;
  }

  function generatorCost(g){
    const n = state.owned[g.id];
    return Math.ceil(g.baseCost * Math.pow(1.15, n));
  }

  function generatorRate(g){
    return g.baseRate * state.owned[g.id] * upgradeMultFor(g.id);
  }

  function totalPerSecond(){
    let total = 0;
    GENERATORS.forEach(g => total += generatorRate(g));
    return total * trophyMultiplier();
  }

  function clickPower(){
    return state.clickValue * upgradeMultFor("click") * trophyMultiplier();
  }

  function promotionThreshold(){
    return 10000 * Math.pow(6, state.league);
  }

  // ---------- Rendering ----------
  const moneyDisplay = document.getElementById("moneyDisplay");
  const ppsDisplay = document.getElementById("ppsDisplay");
  const trophyDisplay = document.getElementById("trophyDisplay");
  const clubName = document.getElementById("clubName");
  const leagueName = document.getElementById("leagueName");
  const prestigeBar = document.getElementById("prestigeBar");
  const prestigePct = document.getElementById("prestigePct");
  const generatorList = document.getElementById("generatorList");
  const upgradeList = document.getElementById("upgradeList");
  const prestigeBtn = document.getElementById("prestigeBtn");
  const prestigeDesc = document.getElementById("prestigeDesc");
  const trophyBonusDesc = document.getElementById("trophyBonusDesc");

  function renderTopBar(){
    moneyDisplay.textContent = fmt(state.money);
    ppsDisplay.textContent = fmt(totalPerSecond()) + "/s";
    trophyDisplay.textContent = state.trophies + " 🏆";
    leagueName.textContent = LEAGUE_NAMES[Math.min(state.league, LEAGUE_NAMES.length-1)];

    const threshold = promotionThreshold();
    const pct = Math.min(100, (state.money / threshold) * 100);
    prestigeBar.style.width = pct + "%";
    prestigePct.textContent = Math.floor(pct) + "%";
  }

  function renderGenerators(){
    generatorList.innerHTML = "";
    GENERATORS.forEach(g => {
      const cost = generatorCost(g);
      const affordable = state.money >= cost;
      const owned = state.owned[g.id];
      const rate = generatorRate(g);

      const card = document.createElement("div");
      card.className = "card" + (affordable ? " affordable" : "");
      card.innerHTML = `
        <div class="card-icon">${g.icon}</div>
        <div class="card-body">
          <div class="card-name">${g.name}</div>
          <div class="card-desc">${g.desc} · ${fmt(rate)}/s total</div>
          ${owned > 0 ? `<div class="card-owned">OWNED: ${owned}</div>` : ""}
        </div>
        <button class="buy-btn" ${affordable ? "" : "disabled"} data-buy="${g.id}">
          <span class="cost">${fmt(cost)}</span>
          <span class="sub">Buy</span>
        </button>
      `;
      generatorList.appendChild(card);
    });
  }

  function renderUpgrades(){
    upgradeList.innerHTML = "";
    UPGRADES.forEach(u => {
      const bought = !!state.upgrades[u.id];
      if(bought) return; // hide once bought to reduce clutter
      const affordable = state.money >= u.cost;
      const card = document.createElement("div");
      card.className = "card" + (affordable ? " affordable" : "");
      card.innerHTML = `
        <div class="card-icon">${u.icon}</div>
        <div class="card-body">
          <div class="card-name">${u.name}</div>
          <div class="card-desc">${u.desc}</div>
        </div>
        <button class="buy-btn" ${affordable ? "" : "disabled"} data-upgrade="${u.id}">
          <span class="cost">${fmt(u.cost)}</span>
          <span class="sub">Buy</span>
        </button>
      `;
      upgradeList.appendChild(card);
    });
    if(upgradeList.children.length === 0){
      upgradeList.innerHTML = `<div class="card"><div class="card-body"><div class="card-name">All upgrades purchased!</div><div class="card-desc">Check back after promoting — new tiers unlock as you grow.</div></div></div>`;
    }
  }

  function renderPrestige(){
    const threshold = promotionThreshold();
    const ready = state.money >= threshold;
    prestigeDesc.textContent = `Reach ${fmt(threshold)} in club funds to earn promotion to the next league. Promoting resets your funds, staff and upgrades — but grants a permanent Trophy that boosts ALL income forever.`;
    prestigeBtn.disabled = !ready;
    prestigeBtn.textContent = ready ? `Promote to ${LEAGUE_NAMES[Math.min(state.league+1, LEAGUE_NAMES.length-1)]} →` : `Need ${fmt(threshold)} (have ${fmt(state.money)})`;
    trophyBonusDesc.textContent = `Each trophy: +10% income, permanently. Current bonus: +${Math.round((trophyMultiplier()-1)*100)}%`;
  }

  function renderAll(){
    renderTopBar();
    renderGenerators();
    renderUpgrades();
    renderPrestige();
  }

  // ---------- Toast ----------
  let toastTimer = null;
  function showToast(msg){
    const t = document.getElementById("toast");
    t.textContent = msg;
    t.classList.add("show");
    clearTimeout(toastTimer);
    toastTimer = setTimeout(() => t.classList.remove("show"), 1800);
  }

  // ---------- Actions ----------
  function buyGenerator(id){
    const g = GENERATORS.find(x => x.id === id);
    if(!g) return;
    const cost = generatorCost(g);
    if(state.money < cost) return;
    state.money -= cost;
    state.owned[id] += 1;
    save();
    renderTopBar(); renderGenerators(); renderPrestige();
  }

  function buyUpgrade(id){
    const u = UPGRADES.find(x => x.id === id);
    if(!u) return;
    if(state.upgrades[id]) return;
    if(state.money < u.cost) return;
    state.money -= u.cost;
    state.upgrades[id] = true;
    save();
    showToast(u.name + " purchased!");
    renderTopBar(); renderGenerators(); renderUpgrades(); renderPrestige();
  }

  function doPrestige(){
    const threshold = promotionThreshold();
    if(state.money < threshold) return;
    state.trophies += 1;
    state.league += 1;
    state.money = 0;
    GENERATORS.forEach(g => state.owned[g.id] = 0);
    state.upgrades = {};
    state.clickValue = 1;
    save();
    showToast("Promoted! +1 Trophy 🏆");
    renderAll();
    switchTab("club");
  }

  const ball = document.getElementById("ball");
  ball.addEventListener("click", (e) => {
    const gain = clickPower();
    state.money += gain;
    state.lifetimeEarned += gain;
    ball.classList.remove("kicked");
    void ball.offsetWidth;
    ball.classList.add("kicked");

    const coin = document.createElement("div");
    coin.className = "float-coin";
    coin.textContent = "+" + fmt(gain);
    const rect = ball.getBoundingClientRect();
    const zoneRect = ball.parentElement.getBoundingClientRect();
    coin.style.left = (rect.left - zoneRect.left + rect.width/2 + (Math.random()*30-15)) + "px";
    coin.style.top = (rect.top - zoneRect.top) + "px";
    ball.parentElement.appendChild(coin);
    setTimeout(() => coin.remove(), 900);

    renderTopBar(); renderGenerators(); renderPrestige();
  });

  generatorList.addEventListener("click", (e) => {
    const btn = e.target.closest("[data-buy]");
    if(btn) buyGenerator(btn.dataset.buy);
  });
  upgradeList.addEventListener("click", (e) => {
    const btn = e.target.closest("[data-upgrade]");
    if(btn) buyUpgrade(btn.dataset.upgrade);
  });
  prestigeBtn.addEventListener("click", doPrestige);

  // ---------- Tabs ----------
  function switchTab(name){
    document.querySelectorAll(".tab").forEach(t => t.classList.toggle("active", t.dataset.tab === name));
    document.getElementById("tab-club").classList.toggle("hidden", name !== "club");
    document.getElementById("tab-upgrades").classList.toggle("hidden", name !== "upgrades");
    document.getElementById("tab-prestige").classList.toggle("hidden", name !== "prestige");
    document.getElementById("mainScroll").scrollTop = 0;
  }
  document.querySelectorAll(".tab").forEach(t => {
    t.addEventListener("click", () => switchTab(t.dataset.tab));
  });

  // ---------- Game Loop ----------
  let lastTick = Date.now();
  function tick(){
    const now = Date.now();
    const dt = (now - lastTick) / 1000;
    lastTick = now;
    const gain = totalPerSecond() * dt;
    if(gain > 0){
      state.money += gain;
      state.lifetimeEarned += gain;
    }
    renderTopBar();
  }
  setInterval(tick, 200);

  // Slower full re-render for buy affordability states
  setInterval(() => { renderGenerators(); renderUpgrades(); renderPrestige(); }, 1000);

  // Offline progress
  function applyOfflineProgress(){
    try{
      const lastSeen = localStorage.getItem(SAVE_KEY + "_lastSeen");
      if(lastSeen){
        const elapsed = Math.min((Date.now() - parseInt(lastSeen,10)) / 1000, 3600 * 8); // cap 8h
        if(elapsed > 5){
          const gain = totalPerSecond() * elapsed;
          if(gain > 0){
            state.money += gain;
            showToast(`Welcome back! Earned ${fmt(gain)} while away.`);
          }
        }
      }
    }catch(e){}
  }
  window.addEventListener("beforeunload", () => {
    try{ localStorage.setItem(SAVE_KEY + "_lastSeen", Date.now().toString()); }catch(e){}
    save();
  });
  document.addEventListener("visibilitychange", () => {
    if(document.visibilityState === "hidden"){ save(); try{ localStorage.setItem(SAVE_KEY + "_lastSeen", Date.now().toString()); }catch(e){} }
  });

  // ---------- Init ----------
  load();
  applyOfflineProgress();
  renderAll();

  // Autosave
  setInterval(save, 5000);

})();
</script>

</body>
</html>

Game Source: Football Empire — Build Your Dynasty

Creator: LunarGalaxy87

Libraries: none

Complexity: complex (625 lines, 23.1 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: football-empire-build-your-dynasty-lunargalaxy87" to link back to the original. Then publish at arcadelab.ai/publish.