'use strict'; /* ================= UI layer ================= */ const $=id=>document.getElementById(id); const UI={ route:'dashboard', /* ---------- shell ---------- */ show(){ $('titleScreen').style.display='none'; $('newGameScreen').style.display='none'; $('gameShell').style.display='grid'; this.go('dashboard'); }, go(route){ this.route=route; document.querySelectorAll('.nav-btn').forEach(b=>b.classList.toggle('active',b.dataset.route===route)); const R={dashboard:this.rDash,squad:this.rSquad,training:this.rTraining,transfers:this.rTransfers, tournaments:this.rTours,matches:this.rMatches,staff:this.rStaff,finance:this.rFinance, rankings:this.rRankings,inbox:this.rInbox}; (R[route]||this.rDash).call(this); this.updateTopbar(); const v=$('view');if(v)v.scrollTop=0; }, updateTopbar(){ if(!G)return; const me=Game.me(); $('orgBadge').innerHTML=`
${U.esc(me.name)}${me.tag} ยท #${Game.rankOf(me.id)||'โ€”'} WORLD
`; $('tbMoney').textContent=U.fmtM(G.money); $('tbMoney').className=G.money<0?'bad':'good'; const net=(Game.lastWeeklyMerch||0)-(Game.lastWeeklyWages||0); $('tbNet').textContent=(net>=0?'+':'')+U.fmtM(net); $('tbNetIc').textContent=net>=0?'๐Ÿ“ˆ':'๐Ÿ“‰'; $('tbNet').style.color=net>=0?'var(--good)':'var(--bad)'; $('tbFans').textContent=U.fmtNum(G.fans); $('tbRank').textContent='#'+(Game.rankOf(me.id)||'โ€”'); $('tbBoard').textContent=Math.round(G.board)+'%'; $('tbBoard').style.color=G.board>50?'var(--good)':G.board>25?'var(--warn)':'var(--bad)'; $('tbDate').textContent=U.fmtDate(G.day)+' ยท S'+G.season; $('tbNextMatch').textContent=this.nextFixtureInfo()||'no match scheduled'; const unread=G.news.filter(n=>!n.read).length; const pill=$('inboxCount');pill.style.display=unread?'':'none';pill.textContent=unread; }, nextFixtureInfo(){ for(const ev of G.calendar){ if(ev.status!=='live')continue; for(const rnd of ev.rounds)for(const m of rnd.matches) if((m.a===G.playerTeamId||m.b===G.playerTeamId)&&!m.winner&&m.a&&m.b) return `${ev.name}: vs ${Game.team(m.a===G.playerTeamId?m.b:m.a).tag} ยท ${U.fmtDateShort(m.day)}`; } const next=G.calendar.find(e=>e.status!=='done'&&e.registered&&e.startDay>G.day); return next?`next: ${next.name} (${U.fmtDateShort(next.startDay)})`:''; }, toast(msg,cls=''){ const t=document.createElement('div');t.className='toast '+cls;t.innerHTML=msg; $('toastRoot').appendChild(t);setTimeout(()=>t.remove(),4200); }, modal(html,wide){ this.closeModal(); const bg=document.createElement('div');bg.className='modal-bg';bg.id='modalBg'; bg.innerHTML=``; bg.addEventListener('click',e=>{if(e.target===bg)bg.remove();}); $('modalRoot').appendChild(bg);return bg; }, closeModal(){const m=$('modalBg');if(m)m.remove();}, /* ---------- advance ---------- */ advance(){ const fx=Game.advanceWeek(); this.updateTopbar(); if(fx)this.openMatchModal(fx); else{ this.refreshRoute(); this.toast(`๐Ÿ“… ${U.fmtDate(G.day)}`); } }, refreshRoute(){this.go(this.route);}, continueAfterMatch(){ this.closeModal(); $('simOverlay').style.display='none'; const fx=Game.remainingAdvance()>0?Game.resumeAdvance():null; if(fx)this.openMatchModal(fx); else this.refreshRoute(); }, /* ================= DASHBOARD ================= */ rDash(){ const me=Game.me(); const rank=Game.rankOf(me.id); const liveEv=G.calendar.find(e=>e.status==='live'&&e.teams.includes(me.id)); const upc=G.calendar.filter(e=>e.status!=='done'&&e.startDay>G.day-10).slice(0,5); const recent=[]; G.calendar.forEach(e=>e.rounds.forEach(r=>r.matches.forEach(m=>{ if(m.a&&m.b&&(m.a===me.id||m.b===me.id)&&m.winner)recent.push({m,e}); }))); recent.sort((a,b)=>b.m.day-a.m.day); const obj=Game.objectives(); let html=`

Club Dashboard

Balance
${U.fmtM(G.money)}
World Rank
#${rank}
Fans
${U.fmtNum(G.fans)}
Reputation
${Math.round(G.rep)}
Board
${Math.round(G.board)}%
Chemistry
${Math.round(me.fam)}%

๐ŸŽฏ Season ${G.season} Objectives

World ranking#${obj.rank} ยท target TOP ${obj.targetRank}
Finances${obj.moneyOk?'solvent โš–๏ธ':'deficit โš ๏ธ'}
Board confidence${obj.board}%
`; html+=`

${liveEv?'๐Ÿ”ด LIVE โ€” '+U.esc(liveEv.name):'๐Ÿ“… Upcoming'}

`; if(liveEv){ html+=this.bracketMini(liveEv)+ ``; }else{ const nxt=G.calendar.find(e=>e.registered&&e.status!=='done'&&e.startDay>G.day); html+=nxt?`

${U.esc(nxt.name)} starts ${U.fmtDate(nxt.startDay)}.

Tier ${nxt.tier} ยท ${U.fmtM(nxt.prize)} to the winner ยท ${nxt.slots} teams${nxt.major?' ยท MAJOR ๐Ÿ†':''}

` :`

No event registered.

`; html+=`

Calendar ahead

`+upc.map(e=> `
${U.esc(e.name)}${e.registered?' โœ“':''}${U.fmtDateShort(e.startDay)} ยท T${e.tier}
`).join(''); } html+=`
`; const sorted=Game.teamPlayers(me).sort((a,b)=>playerOverall(b)-playerOverall(a)); const trophies=Object.entries(G.achievements).filter(([k])=>k.startsWith('trophy_')); const expiring=Game.teamPlayers(me) .filter(p=>p.contractUntil>G.day&&p.contractUntil-G.day<=150) .sort((a,b)=>a.contractUntil-b.contractUntil).slice(0,5); html+=`

๐Ÿ† Trophy Cabinet

${ G.achievements.grandSlam?'

๐Ÿ’Ž GRAND SLAM CHAMPION

':''}${ trophies.length?trophies.map(([k,v])=>`
๐Ÿฅ† ${U.esc(v.ev)}${U.fmtDateShort(v.day)}
`).join('') :'

Empty shelf. The first trophy tastes the sweetest.

' }

โณ Expiring Contracts

${ expiring.map(p=>{const left=p.contractUntil-G.day; return `
${p.nick} ${playerOverall(p).toFixed(0)}${left}d
`;}).join('') ||'

All contracts secure โœ”

' }
`; html+=`

๐Ÿ… Career Milestones

${Game.MILESTONES.map(m=>{ const got=G.milestones.includes(m.id); return `
${got?'โœ…':'๐Ÿ”’'} ${m.name}${got?'unlocked':U.esc(m.desc)}
`; }).join('')}
`; html+=`

Squad Snapshot

${ sorted.slice(0,6).map(p=>``).join('') }
PlayerFormMoraleFatigue
${playerOverall(p).toFixed(0)} ${p.nick} ${FLAG(p.nat)}
${p.role}
${formStars(p.form)} ${faceFor(p.morale)}
`; html+=`

Recent Results

${ recent.slice(0,6).map(({m,e})=>{ const won=m.winner===me.id; return `
${won?'โœ…':'โŒ'} vs ${Game.team(m.a===me.id?m.b:m.a).name}${m.score} ${U.esc(e.name)}
`; }).join('')||'

No matches played yet.

' }
`; html+=`

Latest News

${ G.news.slice(0,6).map(newsRow).join('')}
`; $('view').innerHTML=html; }, bracketMini(ev){ let html=''; ev.rounds.forEach(rnd=>{ rnd.matches.forEach(m=>{ if(!m.a||!m.b)return; const mine=m.a===G.playerTeamId||m.b===G.playerTeamId; html+=`
${Game.team(m.a).tag} vs ${Game.team(m.b).tag}${mine?' โญ':''} ${m.winner?`${m.score} ${rnd.name}`:`${U.fmtDateShort(m.day)}`}
`; }); }); return html||'

Bracket formingโ€ฆ

'; }, /* ================= SQUAD ================= */ rSquad(){ const me=Game.me(); const ps=Game.teamPlayers(me).sort((a,b)=>playerOverall(b)-playerOverall(a)); const rows=ps.map(p=>{ const starter=me.lineup.includes(p.id)?'โญ':''; const left=p.contractUntil-G.day; const ctr=left<45?'bad':left<120?'warn':'muted'; return ` ${playerOverall(p).toFixed(0)} ${starter}${p.nick} ${FLAG(p.nat)}${p.listedPrice!==null?' ๐Ÿ”':''}
${U.esc(p.name)}, ${p.age} ${p.role} ${formStars(p.form)} ${faceFor(p.morale)} ${Math.round(p.morale)}
${p.injuryDays?`๐Ÿฅ${p.injuryDays}d`:''} ${U.fmtM(p.salary)} ${U.fmtM(playerValue(p,G))} ${left<=0?'EXPIRED':left+'d'}`; }).join(''); $('view').innerHTML=`

Squad Management

Starting Lineup

${me.lineup.map((pid,i)=>{const p=Game.p(pid);return `${i+1}. ${p.nick} ${playerOverall(p).toFixed(0)}${p.injuryDays?' ๐Ÿฅ':''}`;}).join('')}
${rows}
OVRPlayerRoleFormMoraleFatigueWage/moValueContract

Roster cap: 8 players (${ps.length}/8).

`; }, playerModal(pid){ const p=Game.p(pid); const mine=p.teamId===G.playerTeamId; const ovr=playerOverall(p); const inLineup=mine&&Game.me().lineup.includes(pid); const left=p.contractUntil-G.day; const attrs=[['Aim',p.attrs.aim],['Positioning',p.attrs.pos],['Clutch',p.attrs.clutch],['Utility',p.attrs.util],['Gamesense',p.attrs.iq],['Movement',p.attrs.move],['Communication',p.attrs.comm]]; const s=p.seasonStats,r=s.maps?s.ratingSum/s.maps:0; const bar=(label,val,max=100)=>`
${label}
`; const html=`
${ovr.toFixed(0)}

${p.nick} ${FLAG(p.nat)}

${U.esc(p.name)} ยท ${p.age}y ยท ${p.role}${p.potential>ovr+4&&mine?` ยท POT ${p.potential}`:''}

Attributes

${attrs.map(([l,vv])=>bar(l,vv)).join('')}
Progress to potential
Form ${formStars(p.form)} ยท Morale ${faceFor(p.morale)} ${Math.round(p.morale)} ยท Fatigue ${Math.round(p.fatigue)}%${p.injuryDays?` ยท Injured ${p.injuryDays}d`:''}
Professionalism ${p.personality.prof} ยท Temperament ${p.personality.temper} ยท Ambition ${p.personality.ambition}

Season Stats

Maps
${s.maps}
Rating
${r?r.toFixed(2):'โ€”'}
K/D
${s.maps&&s.d?(s.k/s.d).toFixed(2):'โ€”'}
ADR
${s.maps?Math.round(s.adr):'โ€”'}
MVPs
${s.mvp}
Clutches
${s.aces}
Contract${mine?(left>0?left+' days left':`expired ${-left}d ago`):U.fmtDate(p.contractUntil)}
Value โ‰ˆ${U.fmtM(playerValue(p,G))}
${mine?`
${inLineup ?`` :``} ${p.listedPrice!==null?`` :``} ${!inLineup&&Game.me().roster.length>5?``:''}
`: `
${p.teamId?`` :``} Wage demand โ‰ˆ ${U.fmtM(wageDemand(p,G))}/mo${p.listedPrice!==null?` ยท asking ${U.fmtM(p.listedPrice)}`:''}
`}`; this.modal(html,true); }, addToLineup(pid){ const me=Game.me(); if(me.lineup.length>=5){this.toast('Lineup is full โ€” bench someone first.','warn');return;} if(!me.lineup.includes(pid))me.lineup.push(pid); Game.checkPromisesAfterLineup(); this.closeModal();this.refreshRoute(); }, confirmRelease(pid){ const p=Game.p(pid); if(confirm(`Release ${p.nick}? A severance payment applies.`)){ const r=Game.releasePlayer(pid); this.toast(r.ok?`${p.nick} released.`:r.msg,r.ok?'good':'bad'); this.closeModal();this.refreshRoute(); } }, listModal(pid){ const p=Game.p(pid); const val=playerValue(p,G); const html=`

Transfer List โ€” ${p.nick}

Market value: ${U.fmtM(val)}. Listed players attract bids from other clubs (see Inbox / Transfers โ†’ Bids).

`; this.modal(html); }, scoutModal(){ const r=Game.scoutYouth(); if(r.ok===false){UI.toast(r.msg,'warn');return;} this.updateTopbar(); const card=p=>{ const ovr=playerOverall(p); const attrs=[['AIM',p.attrs.aim],['POS',p.attrs.pos],['CLU',p.attrs.clutch],['UTL',p.attrs.util],['IQ',p.attrs.iq],['MOV',p.attrs.move],['COM',p.attrs.comm]]; return `
${FLAG(p.nat)} ${p.nick} ${p.age}y ยท ${p.role} ยท ${NATIONS[p.nat]||p.nat}
${ovr.toFixed(0)} POT ${p.potential}โ˜…
${attrs.map(([l,v])=>`${l} ${v.toFixed(0)}`).join(' ยท ')}
`; }; this.modal(`

๐Ÿ” Scouting Camp โ€” $80K

Three unattached youngsters caught our scouts' eyes. Sign one โ€” the others return to obscurity. Potential scales with club reputation.

${r.list.map(card).join('')}
`); }, loanModal(pid){ const p=Game.p(pid); const clubs=Object.values(G.teams).filter(t=>!t.isPlayer&&t.id!==p.teamId) .sort((a,b)=>a.ratingPts-b.ratingPts).slice(0,8); const html=`

Development Loan โ€” ${p.nick}

Loaning a young player out for 90 days earns a small fee and accelerates his growth through playing time. He returns automatically afterwards.

${clubs.map(t=>`
${U.esc(t.name)} Tier ${t.tier} ยท #${Game.rankOf(t.id)} ยท roster ${t.roster.length}/8
`).join('')}
`; this.modal(html); }, /* ---------- talk module ---------- */ talkModal(pid){ const p=Game.p(pid); const topics=[['praise','๐Ÿ‘ Praise his form'],['motivate','๐Ÿ”ฅ Motivate him'],['role','๐Ÿงฉ Discuss role'], ['discipline','โš ๏ธ Demand professionalism'],['promise','๐Ÿค Promise a starting spot'],['contract','๐Ÿ’ต Hint at a raise']]; const tones=['supportive','neutral','demanding']; const html=`

Talk to ${p.nick}

Morale ${faceFor(p.morale)} ${Math.round(p.morale)} ยท Motivation ${Math.round(p.motivation)} ยท Form ${formStars(p.form)}. One meaningful talk per ~10 days.

Topic

${ topics.map(([id,l])=>``).join('') }

Tone

${tones.map((t,i)=>``).join('')}
`; this.modal(html); $('doTalk').onclick=()=>{ const topic=document.querySelector('#talkTopics .on')?.dataset.t; const tone=document.querySelector('#talkTones .on')?.dataset.t||'neutral'; if(!topic){$('talkResult').innerHTML='

Pick a topic first.

';return;} const res=Game.applyTalk(pid,topic,tone); $('talkResult').innerHTML=`

${U.esc(res.msg)}

${res.dM!==undefined?`

Morale ${res.dM>=0?'+':''}${res.dM} ยท Motivation ${res.dMo>=0?'+':''}${res.dMo}

`:''}
`; this.updateTopbar(); }; }, /* ---------- negotiation ---------- */ negotiateModal(pid,kind){ const p=Game.p(pid); const demand=wageDemand(p,G); const defSalary=Math.max(kind==='renew'?p.salary:0,Math.round(demand/1000)*1000); const title=kind==='renew'?`Renew โ€” ${p.nick}`:(kind==='fa'?`Sign free agent ${p.nick}`:`Bid for ${p.nick} (${U.esc(Game.team(p.teamId)?.tag||'')})`); const feeField=kind==='buy'?``:''; const html=`

${title}

His camp wants โ‰ˆ ${U.fmtM(demand)}/mo. Current club: ${p.teamId?U.esc(Game.team(p.teamId).name):'free agent'} ยท morale ${faceFor(p.morale)} ยท OVR ${playerOverall(p).toFixed(0)}

${feeField}
`; this.modal(html); $('negGo').onclick=()=>{ const salary=parseInt($('negSalary').value)||demand; const years=U.clamp(parseInt($('negYears').value)||2,1,5); const bonus=parseInt($('negBonus').value)||0; let res; if(kind==='renew')res=Game.renewContract(pid,salary,years,bonus); else if(kind==='fa')res=Game.signFreeAgent(pid,salary,years,bonus); else{ const fee=parseInt($('negFee')?.value)||playerValue(p,G); res=Game.makeTransferOffer(pid,fee,salary,years); } if(res.ok){this.toast(res.msg,'good');this.closeModal();this.refreshRoute();} else $('negResult').innerHTML=`
โŒ ${U.esc(res.msg)}
`; }; }, /* ================= TRAINING ================= */ rTraining(){ const me=Game.me(); const coach=Game.staffOf(me,'Coach'),physio=Game.staffOf(me,'Physio'); const rowHtml=Game.teamPlayers(me).map(p=>{ const focusSel=TRAINING_FOCUS.map(f=>``).join(''); const intSel=[[0,'Light'],[1,'Normal'],[2,'Hard']].map(([v,l])=>``).join(''); return ` ${p.nick} ${p.role} ${playerOverall(p).toFixed(0)} โ†’ ${p.potential}
${p.injuryDays?`๐Ÿฅ ${p.injuryDays}d`:'โœ”'}`; }).join(''); const bootcamping=(G.flags.bootcampUntil||0)>G.day; $('view').innerHTML=`

Training Center

Weekly Plan

Focus each player's week. Hard intensity grows skills fastest but piles up fatigue โ€” injury risk rises above 86%. Rest recovers body and mind. Media weeks grow your fanbase.

Support Effects

Coach${coach?`${coach.nick} (+${Math.round(coach.skill-55)}% growth)`:'vacant'}
Physio${physio?`${physio.nick} (faster recovery)`:'vacant'}
Chemistry${Math.round(me.fam)}%
${bootcamping?'

Bootcamp in progressโ€ฆ

':''}
${rowHtml}
PlayerLevelFocusIntensityFatigueStatus
`; }, doBootcamp(){ const r=Game.bootcamp(); if(r==='nomoney')this.toast('Not enough money.','bad'); else if(r===false)this.toast('Already bootcamping.','warn'); else this.toast('Bootcamp booked โ€” chemistry rising.','good'); this.refreshRoute(); }, /* ================= TRANSFERS ================= */ _marketFilters:{q:'',role:'',maxAge:99,minOvr:0,sortBy:'ovr'}, rTransfers(){ const f=this._marketFilters; const tab=G.ui.marketTab||'market'; const win=Game.inTransferWindow(); const d=((G.day-1)%364)+1; let winTxt; if(d>=150&&d<=230)winTxt=`window closes in ${230-d}d`; else if(d>=300)winTxt=`winter window ยท ${365-d}d left`; else winTxt=`opens in ${150-d}d`; let body=''; if(tab==='offers'){ body=G.pendingOffers.map((b,i)=>{ const p=Game.p(b.pid),from=Game.team(b.from); return `
${p.nick} ${p.role} ยท ${playerOverall(p).toFixed(0)} OVR ยท value ${U.fmtM(playerValue(p,G))}
${U.esc(from.name)} offers ${U.fmtM(b.fee)} + ${U.fmtM(b.wage)}/mo wages
`; }).join('')||'

No incoming bids right now.

'; }else{ let list=Object.values(G.players).filter(p=>{ if(p.loanFrom)return false; const isFA=p.teamId===null; const isListed=p.listedPrice!==null&&p.teamId!==G.playerTeamId; if(!isFA&&!isListed)return false; if(f.role&&p.role!==f.role)return false; if(p.age>f.maxAge)return false; if(playerOverall(p)playerOverall(b)-playerOverall(a),val:(a,b)=>playerValue(b,G)-playerValue(a,G),age:(a,b)=>a.age-b.age,pot:(a,b)=>b.potential-a.potential}; list.sort(sorters[f.sortBy]); body=list.slice(0,60).map(p=>{ const isFA=p.teamId===null; return ` ${playerOverall(p).toFixed(0)} ${p.nick} ${FLAG(p.nat)}
${U.esc(p.name)}, ${p.age} ${p.role} ${p.potential} ${U.fmtM(playerValue(p,G))} ${isFA?'FREE AGENT':U.esc(Game.team(p.teamId)?.tag||'โ€”')} ${isFA?U.fmtM(wageDemand(p,G))+'/mo':(p.listedPrice?U.fmtM(p.listedPrice):'โ€”')}`; }).join('')||'Nobody matches those filters.'; } $('view').innerHTML=`

Transfer Center ยท ${win?'๐ŸŸข transfer window open':'โšช closed'} (${winTxt})

${tab==='market'?` `:''}
${tab==='market'?`
${body}
OVRPlayerRolePotValueClubPrice
`:`
${body}
`}`; }, /* ================= TOURNAMENTS ================= */ rTours(){ const me=Game.me(); const evs=G.calendar.filter(e=>e.startDay>G.day-40).sort((a,b)=>a.startDay-b.startDay); const groups={open:[],locked:[],live:[],done:[]}; evs.forEach(e=>groups[e.status]?.push(e)); const card=e=>{ const regd=e.registered,inField=e.teams.includes(me.id); return `

${U.esc(e.name)} ${e.major?'๐Ÿ†':''}${e.gs?' ๐Ÿ’Ž':''}

Tier ${e.tier}
Starts${U.fmtDate(e.startDay)}
Winner prize${U.fmtM(e.prize)}
Points${e.pts}
Teams${e.teams.length||e.slots}
${e.status==='open'?(regd?'

โœ“ Registered โ€” field locks soon

' :``) :e.status==='live'?`

โ— LIVE${inField?' โ€” you are competing!':''}

` :`

Champion: ${e.champion?U.esc(Game.team(e.champion).name):'โ€”'}

`}
`; }; $('view').innerHTML=`

Tournament Circuit

${G.achievements.grandSlam?'

๐Ÿ’Ž GRAND SLAM CHAMPION ๐Ÿ’Ž

':`

Win ${GS_REQUIRED} of the ๐Ÿ’Ž elite events consecutively within a year for the Grand Slam (${U.fmtM(GS_BONUS)} bonus).

`} ${groups.live.length?`

Happening now

${groups.live.map(card).join('')}
`:''}

Upcoming & open

${groups.open.concat(groups.locked).map(card).join('')||'

Nothing open right now.

'}

Recently finished

${groups.done.slice(-4).reverse().map(card).join('')||'

โ€”

'}
`; }, registerEvent(id){ const r=Game.registerEvent(id); if(r&&!r.ok)this.toast(r.msg,'warn'); else this.toast('Registered โœ”','good'); this.refreshRoute(); }, eventDetail(id){ const ev=G.calendar.find(e=>e.id===id);if(!ev)return; let html=`

${U.esc(ev.name)} ${ev.major?'๐Ÿ†':''}

${U.fmtDate(ev.startDay)} ยท Tier ${ev.tier} ยท ${U.fmtM(ev.prize)} winner prize ยท ${ev.pts} pts

`; if(ev.champion)html+=`

๐Ÿฅ‡ Champion: ${U.esc(Game.team(ev.champion).name)}

`; html+='
'; ev.rounds.forEach(rnd=>{ html+=`

${rnd.name} ${U.fmtDateShort(rnd.day)}

`; rnd.matches.forEach(m=>{ if(!m.a||!m.b){html+=`
TBD vs TBD
`;return;} const A=Game.team(m.a),B=Game.team(m.b); const mine=m.a===G.playerTeamId||m.b===G.playerTeamId; const winIsA=m.winner===m.a; html+=`
${winIsA?'':''}${A.tag}${winIsA?'':''} vs ${m.winner&&!winIsA?'':''}${B.tag}${m.winner&&!winIsA?'':''} ${mine?'โญ':''} ${m.winner?`${m.score} ${(m.maps||[]).map(x=>`${x.map} ${x.score}`).join(' ยท ')}`:`${U.fmtDateShort(m.day)} ยท BO${m.bo}`}
`; }); }); html+='
'; const pending=Game.findUserFixtureToday(); html+=`
${pending&&ev.status==='live'?``:''}
`; this.modal(html,true); }, /* ================= MATCHES ================= */ rMatches(){ const me=Game.me(); const past=[];const future=[]; G.calendar.forEach(e=>e.rounds.forEach(r=>r.matches.forEach(m=>{ if(!m.a||!m.b)return; if(m.a===me.id||m.b===me.id)(m.winner?past:future).push({m,e}); }))); past.sort((a,b)=>b.m.day-a.m.day); const opp=m=>Game.team(m.a===me.id?m.b:m.a); $('view').innerHTML=`

Matches

Upcoming

${ future.sort((a,b)=>a.m.day-b.m.day).map(({m,e})=>`
vs ${U.esc(opp(m).name)} BO${m.bo} ยท ${U.esc(e.name)} ${U.fmtDate(m.day)}
`).join('')||'

Register for events to schedule matches.

'}

History

${past.map(({m,e})=>{ const won=m.winner===me.id; return ``; }).join('')}
DateEventOpponentResultScore
${U.fmtDateShort(m.day)}${U.esc(e.name)} ${U.esc(opp(m).tag)}${won?'WIN':'LOSS'}${m.score}
${past.length?'':'

No matches yet.

'}
`; }, boxScoreModal(evId,mid){ const ev=G.calendar.find(e=>e.id===evId);if(!ev)return; let m=null; for(const r of ev.rounds)for(const x of r.matches)if(x.id===mid)m=x; if(!m||!m.winner)return; this.boxScoreInline(m,ev); }, boxScoreInline(m,ev,opts={}){ const la=MatchEngine.lineupOf(m.a),lb=MatchEngine.lineupOf(m.b); const tbl=(tid,lineup)=>`

${U.esc(Game.team(tid).name)} ${m.winner===tid?'๐Ÿ†':''}

${lineup.map(pid=>{const b=m.box[pid];if(!b)return'';const p=Game.p(pid); const rt=(b.ratingSum||1)/Math.max(b.mapsN||1,1); return ``; }).join('')}
PlayerKDADRRating
${m.mvp===pid?'๐Ÿ… ':''}${p.nick} ${b.k}${b.d}${Math.round(b.adr/Math.max(b.mapsN||1,1))}${rt.toFixed(2)}
`; const scoreParts=String(m.score).split('-'); this.modal(`

${U.esc(Game.team(m.a).tag)} ${U.esc(scoreParts[0]||'')} : ${U.esc(scoreParts[1]||'')} ${U.esc(Game.team(m.b).tag)}

${ev?U.esc(ev.name||'')+' ยท ':''}${(m.maps||[]).map(x=>`${x.map} ${x.score}`).join(' ยท ')}

${tbl(m.a,la)}${tbl(m.b,lb)}
${opts.continueBtn?``:''}
`,true); }, /* ================= STAFF ================= */ rStaff(){ const me=Game.me(); const ROLES_STAFF=['Coach','Analyst','Psychologist','Physio','Media Manager','Finance Director']; const cur=ROLES_STAFF.map(role=>{ const s=Game.staffOf(me,role); return `

${role}

${s? `
${s.nick} ${U.esc(s.name)}
${'โ˜…'.repeat(Math.round(s.skill/20))} ${s.skill} skill ยท ${U.fmtM(s.salary)}/mo
` :'

Vacant

'}
`; }).join(''); const pool=Object.values(G.staff).filter(s=>!s.teamId).sort((a,b)=>b.skill-a.skill); $('view').innerHTML=`

Backroom Staff

Coaches accelerate training, analysts sharpen prep, psychologists stabilize morale, physios cut fatigue, media managers grow fans, finance directors find savings & better sponsors.

${cur}

Available Staff

${pool.map(s=>``).join('')||''}
NameRoleSkillSalary/mo
${s.nick} ${U.esc(s.name)} ${s.role}${'โ˜…'.repeat(Math.round(s.skill/20))} ${s.skill} ${U.fmtM(s.salary)}
Market is empty โ€” check back later.
`; }, hireStaff(sid){ const s=G.staff[sid];const me=Game.me(); if(G.moneys+G.players[pid].salary,0)+Object.values(me.staff).reduce((s,sid)=>s+(G.staff[sid]?.salary||0),0); const merchMo=Math.round((Game.lastWeeklyMerch||0)*4.33); const sponsMo=G.sponsors.reduce((s,x)=>s+x.monthly,0); const ops=me.tier===4?30000:45000; const net=merchMo+sponsMo-wagesMo-ops; $('view').innerHTML=`

Finances

Balance
${U.fmtM(G.money)}
Monthly Net
${net>=0?'+':''}${U.fmtM(net)}
Wages+Staff
-${U.fmtM(wagesMo)}
Ops Cost
-${U.fmtM(ops)}
Merch/Stream
+${U.fmtM(merchMo)}
Sponsors
+${U.fmtM(sponsMo)}

Active Partnerships (${G.sponsors.length}/3)

${ G.sponsors.map(s=>`
${U.esc(s.brand)} ${U.esc(s.type)}${U.fmtM(s.monthly)}/mo ยท ${Math.ceil(Math.max(0,(s.until-G.day))/28)}mo left
`).join('')||'

None yet.

'}

Pending Offers

${ G.sponsorOffers.map(o=>`
${U.esc(o.brand)} ${U.esc(o.type)}
${U.fmtM(o.monthly)}/mo ร— ${o.months} months
`).join('')||'

New offers arrive periodically based on ranking, rep & fans.

'}
`; }, /* ================= RANKINGS ================= */ rRankings(){ const prevIdx=id=>G.rankingPrev.indexOf(id); const rows=G.ranking.map((tid,i)=>{ const t=G.teams[tid]; const pi=prevIdx(tid); const move=pi>=0?pi-i:null; const arrow=move===null?'โ€ข':move>0?`โ–ฒ${move}`:move<0?`โ–ผ${-move}`:'โ€“'; return ` #${i+1}${arrow} ${U.esc(t.name)} ${t.isPlayer?'โญ':''} ${t.region}${Math.round(t.ratingPts)} ${t._lastPts?`+${t._lastPts}`:''}`; }).join(''); const topPlayers=Object.values(G.players).filter(p=>p.seasonStats.maps>=3) .sort((a,b)=>(b.seasonStats.ratingSum/b.seasonStats.maps)-(a.seasonStats.ratingSum/a.seasonStats.maps)).slice(0,15); $('view').innerHTML=`

World Rankings

${rows}
#TeamRegionPointsLast EV

Player Leaderboard (season rating)

${topPlayers.map((p,i)=>{ const rt=p.seasonStats.ratingSum/p.seasonStats.maps; return ``; }).join('')||''}
#PlayerClubMapsRating
${i+1}${p.nick} ${FLAG(p.nat)} ${U.esc(Game.team(p.teamId)?.tag||'FA')}${p.seasonStats.maps}${rt.toFixed(2)}
Play some maps!
${G.history.length?`

Season History

${G.history.map(h=>``).join('')}
SeasonFinal rankBalanceFansTrophies
S${h.season}#${h.rank}${U.fmtM(h.money)}${U.fmtNum(h.fans)}${h.trophies||0}
`:''}`; }, /* ================= INBOX ================= */ rInbox(){ const items=G.news.slice(0,60); $('view').innerHTML=`

Inbox

${items.map(n=>newsRow(n,true)).join('')||'

Empty.

'}
`; }, /* ================= MATCH MODALS & SIMULATION MODE ================= */ openMatchModal(fx){ window.__fx=fx; const pv=MatchEngine.preview(fx); const opp=Game.team(fx.a===G.playerTeamId?fx.b:fx.a); const evName=fx.evId?G.calendar.find(e=>e.id===fx.evId)?.name:'Exhibition'; const html=`

โš”๏ธ ${U.esc(Game.me().name)} vs ${U.esc(opp.name)}

${U.esc(evName||'')} ยท BO${fx.bo} ยท ${U.fmtDate(fx.day)}

${U.esc(Game.me().tag)} #${pv.rankA}

Avg OVR ${pv.sA.toFixed(1)}

${Game.lineupPlayers(Game.me()).map(p=>`${p.nick} ${playerOverall(p).toFixed(0)}${p.injuryDays?' ๐Ÿฅ':''}`).join('')}

${U.esc(opp.tag)} #${pv.rankB}

Avg OVR ${pv.sB.toFixed(1)}

${MatchEngine.lineupOf(opp.id).map(pid=>`${Game.p(pid).nick}`).join('')}

Estimated win chance: ${Math.round(pv.pWin*100)}%

Simulation mode: live map veto, buy decisions, round tactics & timeouts. Quick sim lets your coach handle it.

`; this.modal(html,true); }, quickSim(){ const fx=window.__fx; const res=MatchEngine.simulate(fx,fx.evId?G.calendar.find(e=>e.id===fx.evId):null); this.showPostMatch(res); }, launchSim(){ this.closeModal(); SimSession.start(window.__fx,window.__fx.evId?G.calendar.find(e=>e.id===window.__fx.evId):null); $('simOverlay').style.display='flex'; this.renderSim(); }, renderSim(){ const S=SimSession.s;if(!S)return; const w=$('simWrap'); if(S.phase==='veto'||S.phase==='pregame'){w.innerHTML=this.simVetoHtml(S);} else if(S.phase==='intermission'){w.innerHTML=this.simInterHtml(S);} else if(S.phase==='done'){w.innerHTML=this.simDoneHtml(S);} else{w.innerHTML=this.simRoundHtml(S);} w.querySelectorAll('.sim-feed').forEach(f=>f.scrollTop=f.scrollHeight); // static minimap preview between rounds (a live anim owns the canvas otherwise) if(S.phase==='round'&&S.awaiting!=='anim'&&typeof VIZ!=='undefined'&&VIZ.ok&&!VIZ.R){ const c=S.cur;if(!c)return; const ctIsA=MatchEngine.sideOf(c,'a',c.round); const roster=c.aLineup.concat(c.bLineup).map(pid=>({pid,nick:Game.p(pid).nick, side:c.aLineup.includes(pid)?'a':'b',role:Game.p(pid).role,dies:false,order:0,killCount:0})); try{VIZ.preview(c.map,roster,ctIsA?'a':'b');}catch(e){} } }, /* ---------- ๐Ÿค– AUTO-SPECTATE: watch the whole series hands-free ---------- */ toggleAuto(){ G.ui.autoSim=!G.ui.autoSim; this.toast(G.ui.autoSim?'๐Ÿค– Auto-spectate ON โ€” enjoy the show':'โธ Auto-spectate paused'); if(G.ui.autoSim){this.renderSim();this.autoTick();} }, autoTick(){ if(!G.ui.autoSim)return; const S=SimSession.s;if(!S){G.ui.autoSim=false;return;} try{ if(S.phase==='done'){ G.ui.autoSim=false; }else if(S.phase==='veto'){ if(SimSession.vetoDone())SimSession.beginMap(0); else if(SimSession.myTurn()){ // ban/pick the best own-affinity map still in the pool const me=Game.me(); let best=null,bv=-99; ACTIVE_MAPS.forEach(m=>{ if(!S.veto.pool.has(m))return; const v=me.mapAff[m]||70; if(v>bv){bv=v;best=m;} }); if(best)SimSession.vetoAction(best); } }else if(S.phase==='pregame'){ SimSession.beginMap(0); }else if(S.phase==='intermission'){ SimSession.beginMap(S.results.length); }else if(S.phase==='round'){ const c=S.cur; if(S.awaiting==='buy')SimSession.chooseBuy(MatchEngine.autoBuy(c,S.userId)); else if(S.awaiting==='tactic')SimSession.chooseTactic(U.pick(['default','default','default','aggr','patient','fake'])); else if(S.awaiting==='next')SimSession.nextRound(); // awaiting 'anim' โ†’ let the animation play, just wait } }catch(e){} // never rebuild the DOM while an animation owns the canvas if(!(S.phase==='round'&&S.awaiting==='anim'))this.renderSim(); if(G.ui.autoSim)setTimeout(()=>this.autoTick(),460); }, simVetoHtml(S){ const v=S.veto; const done=SimSession.vetoDone(); const myTurn=!done&&SimSession.myTurn(); const isBan=S.veto.step
${U.esc(Game.team(S.aId).name)}
MAP VETO ยท BO${S.bo}
๐Ÿ—ณ
${U.esc(Game.team(S.bId).name)}
MAP VETO ยท BO${S.bo}
${done?`

Map pool set: ${v.seq.join(' โ†’ ')}

` :myTurn?`

Your move: ${isBan?'๐Ÿšซ BAN':'โœ… PICK'} a map.

` :`

${aiTag} is decidingโ€ฆ

`} ${!done?`
${ACTIVE_MAPS.map(mp=>{ const gone=!v.pool.has(mp); const aff=Game.me().mapAff[mp]||0; const picked=v.seq.includes(mp); return `
${mp}
${aff>=0?'+':''}${aff.toFixed?aff.toFixed(1):aff} aff${picked?'
picked':''}
`; }).join('')}
`:''}
${v.log.map(l=>`

${l}

`).join('')}
`; }, simInterHtml(S){ const c=S.cur; const last=S.results[S.results.length-1]; return `
${U.esc(Game.team(S.aId).name)}
SERIES
${S.scoreA}-${S.scoreB}
${U.esc(Game.team(S.bId).name)}

${last.map} โ€” final score

${last.sa}-${last.sb}

${c.log.map(l=>l.startsWith('๐Ÿ”ซ')?`

${l}

`:`

${l}

`).join('')}
`; }, simDoneHtml(S){ const win=S.scoreA>S.scoreB?S.aId:S.bId; const iWon=win===G.playerTeamId; const mvpP=S.m.mvp?Game.p(S.m.mvp):null; return `
${U.esc(Game.team(S.aId).name)}
FINAL
${S.scoreA}-${S.scoreB}
${U.esc(Game.team(S.bId).name)}

${iWon?'๐Ÿ† VICTORY!':'๐Ÿ’€ DEFEAT'}

MVP: ${mvpP?`${mvpP.nick}`:'โ€”'}

${S.results.map(r=>`
${r.map}${r.sa}-${r.sb}
`).join('')}
`; }, showBoxFromSim(){ const S=SimSession.s; if(S&&S.m&&S.m.winner)this.boxScoreInline(S.m,S.ev,{continueBtn:true}); }, simRoundHtml(S){ const c=S.cur,k=S.userId; const opT=Game.team(k==='a'?S.bId:S.aId); const myScore=k==='a'?c.sa:c.sb,opScore=k==='a'?c.sb:c.sa; const moPos=50+(k==='a'?c.momentum:-c.momentum)*5; const buyOpts=SimSession.buyOptions(); let controls=''; if(S.awaiting==='buy'){ controls=`
Your team funds$${U.fmtNum(c.fund[k])}
${buyOpts.map(o=>``).join('')}
${c.timeouts[k]>0?``:''}

Tip: eco when under 6.8K, force with 6.8โ€“11.8K. Mix tactics โ€” smart opponents read patterns.

`; }else if(S.awaiting==='tactic'){ controls=`

Buy locked: ${String(S.userBuy).toUpperCase()}. Choose your approach:

${TACTICS.map(t=>``).join('')}
`; }else if(S.awaiting==='anim'){ controls=`
๐Ÿ”ด LIVE
`; }else if(S.awaiting==='next'){ controls=` `; } const ctIsA=MatchEngine.sideOf(c,'a',c.round); const lineupChips=(k==='a'?c.aLineup:c.bLineup).map(pid=>{ const p=Game.p(pid);const b=c.box[pid]; return `${p.nick} ${b?`${b.k}/${b.d}`:'0/0'}${p.injuryDays?' ๐Ÿฅ':''}`; }).join(''); return `
${U.esc(Game.team(S.aId).name)}
${U.esc(c.map)} ยท Round ${c.round+1} ยท MR12
${c.sa} : ${c.sb}
You: ${myScore} โ€” ${opT.tag}: ${opScore} ยท OT target ${c.winScore}
${U.esc(Game.team(S.bId).name)}
${S.veto.seq.map((mp,i)=> {const r=S.results[i]; return `${mp}${r?` ${r.sa}-${r.sb}`:''}`;}).join(' ยท ')}
${ctIsA?'Your side: CT โ€” hold the sites':'Your side: T โ€” execute onto a site'}

${S.awaiting==='next'||S.awaiting==='anim'?'Round over':'Command the round'}

${controls}
${lineupChips} ๐ŸŽ™ ${c.timeouts[k]} timeouts left
${c.log.map(l=>l.startsWith('๐Ÿ”ซ')?`

${l}

`:`

${l}

`).join('')}
`; }, /* ---------- post match (quick sim) ---------- */ showPostMatch(res){ const iWon=res.winner===G.playerTeamId; const mvp=res.mvp?Game.p(res.mvp):null; const b=res.box[res.mvp]; const html=`

${iWon?'๐Ÿ† VICTORY':'๐Ÿ’€ DEFEAT'} โ€” ${U.esc(Game.team(res.winner).name)} win ${res.score}

${res.maps.map(x=>`${x.map} ${x.score}`).join(' ยท ')}${res.note||''}

MVP: ${mvp?`${mvp.nick} (${(b.ratingSum/b.mapsN).toFixed(2)})`:'โ€”'}

`; this.modal(html,true); }, boxFromQuick(){ const fx=window.__fx; if(fx&&fx.winner)this.boxScoreInline(fx,fx.evId?G.calendar.find(e=>e.id===fx.evId):null,true); }, /* ---------- save menu ---------- */ saveModal(){ const html=`

Career Menu

Autosave runs every in-game week. Saves live in your browser's localStorage.

`; this.modal(html); }, /* ---------- new game wizard ---------- */ showNewGame(){ $('titleScreen').style.display='none'; $('newGameScreen').style.display='block'; Game.newWorld(document.querySelector('input[name=difficulty]:checked')?.value||'normal'); const list=$('teamPickList'); list.innerHTML=Object.values(G.teams).filter(t=>t.tier<=2).sort((a,b)=>b.tier-a.tier).map(t=>{ const best=Game.teamPlayers(t).sort((a,b)=>playerOverall(b)-playerOverall(a)).slice(0,3); return `
${U.esc(t.name)}
${t.region} ยท Tier ${t.tier}Avg ${(Game.teamPlayers(t).reduce((s,p)=>s+playerOverall(p),0)/5).toFixed(0)} OVR
${best.map(p=>p.nick).join(' ยท ')}
`; }).join(''); }, pickTeam(teamId){ const diff=document.querySelector('input[name=difficulty]:checked')?.value||'normal'; Game.newWorld(diff); Game.startWithTeam(teamId); this.enterGame(); }, enterGame(){ $('newGameScreen').style.display='none'; $('titleScreen').style.display='none'; this.show(); Save.save(true); this.toast('Career started. Good luck, GM! ๐ŸŽ™๏ธ','good'); }, }; function newsRow(n,full){ const ACT={bid:['๐Ÿ” Review bids',"G.ui.marketTab='offers';UI.go('transfers')"], renew:null,sponsor:['๐Ÿ’ผ Finances',"UI.go('finance')"],event:['๐Ÿ† Tournaments',"UI.go('tournaments')"]}; let act=''; if(full&&n.action){ if(n.action.kind==='renew'&&n.action.pid){ act=`
`; }else if(ACT[n.action.kind]){ act=`
`; } } return `
${U.fmtDateShort(n.day)}
${U.esc(n.title)}
${full?`
${n.body}
`:''} ${act} ${n.tag}
`; }