⚠️ GAME RUNNING

You have a game still running. End it and start the new game?

AEU SUPERARCADE

Loading CRM ticker…
Player | CHESS 0 PLAYS
Player
CHESS 0 PLAYS
📺 MVPs TV LIVE
No live matches right now
LIVE
Open tournaments
🏟️Loading open tournaments…
My upcoming matches
⚔️Loading your matches…
Create on the website

Arcade does not create tournaments. Organizers build events on AEU, then they show up here.

Open create on website
No Track
0:00
0:00
🔊
Playlist
BROWSE
ENTER START
ESC BACK
/* ═══════════ Music Player ═══════════ */ (function(){ const playlist=[]; let currentIdx=-1; let audio=new Audio(); let isPlaying=false; const panel=document.getElementById('music-panel'); const titleEl=document.getElementById('music-title'); const artistEl=document.getElementById('music-artist'); const albumArt=document.getElementById('music-album-art'); const playBtn=document.getElementById('music-play'); const prevBtn=document.getElementById('music-prev'); const nextBtn=document.getElementById('music-next'); const progressBar=document.getElementById('music-progress'); const progressFill=document.getElementById('music-progress-fill'); const timeCur=document.getElementById('music-time-cur'); const timeDur=document.getElementById('music-time-dur'); const volumeSlider=document.getElementById('music-volume'); const volIcon=document.getElementById('music-vol-icon'); const playlistEl=document.getElementById('music-playlist'); const addBtn=document.getElementById('music-add-btn'); const fileInput=document.getElementById('music-file-input'); audio.volume=0.6; function fmtTime(s){ if(!s||isNaN(s)) return '0:00'; const m=Math.floor(s/60); const sec=Math.floor(s%60); return m+':'+(sec<10?'0':'')+sec; } function renderPlaylist(){ playlistEl.innerHTML=''; playlist.forEach((t,i)=>{ const row=document.createElement('div'); row.className='music-track'+(i===currentIdx?' active':''); row.innerHTML= ''+(i+1)+''+ ''+t.name+''+ ''+(t.duration||'--:--')+''; row.onclick=()=>playTrack(i); playlistEl.appendChild(row); }); } function loadTrack(i){ if(i<0||i>=playlist.length) return; currentIdx=i; const t=playlist[i]; audio.src=t.url; titleEl.textContent=t.name; artistEl.textContent=t.artist||'Unknown Artist'; progressFill.style.width='0%'; timeCur.textContent='0:00'; timeDur.textContent=t.duration||'0:00'; renderPlaylist(); } function playTrack(i){ loadTrack(i); audio.play(); isPlaying=true; playBtn.textContent='⏸'; panel.classList.add('playing'); } function togglePlay(){ if(playlist.length===0) return; if(currentIdx<0) { playTrack(0); return; } if(isPlaying){ audio.pause(); isPlaying=false; playBtn.textContent='▶'; panel.classList.remove('playing'); } else { audio.play(); isPlaying=true; playBtn.textContent='⏸'; panel.classList.add('playing'); } } function nextTrack(){ if(playlist.length===0) return; playTrack((currentIdx+1)%playlist.length); } function prevTrack(){ if(playlist.length===0) return; if(audio.currentTime>3){ audio.currentTime=0; return; } playTrack((currentIdx-1+playlist.length)%playlist.length); } playBtn.onclick=togglePlay; nextBtn.onclick=nextTrack; prevBtn.onclick=prevTrack; audio.addEventListener('timeupdate',()=>{ if(!audio.duration) return; const pct=(audio.currentTime/audio.duration)*100; progressFill.style.width=pct+'%'; timeCur.textContent=fmtTime(audio.currentTime); }); audio.addEventListener('loadedmetadata',()=>{ timeDur.textContent=fmtTime(audio.duration); if(playlist[currentIdx]) playlist[currentIdx].duration=fmtTime(audio.duration); renderPlaylist(); }); audio.addEventListener('ended',nextTrack); progressBar.onclick=function(e){ if(!audio.duration) return; const pct=e.offsetX/this.offsetWidth; audio.currentTime=pct*audio.duration; }; volumeSlider.oninput=function(){ audio.volume=this.value/100; volIcon.textContent=audio.volume===0?'🔇':audio.volume<0.4?'🔉':'🔊'; }; volIcon.onclick=function(){ if(audio.volume>0){ audio._prevVol=audio.volume; audio.volume=0; volumeSlider.value=0; volIcon.textContent='🔇'; } else { audio.volume=audio._prevVol||0.6; volumeSlider.value=Math.round(audio.volume*100); volIcon.textContent='🔊'; } }; addBtn.onclick=()=>fileInput.click(); fileInput.onchange=function(){ const files=Array.from(this.files); files.forEach(f=>{ const url=URL.createObjectURL(f); const name=f.name.replace(/\.[^.]+$/,''); playlist.push({name,artist:'Local File',url,duration:null}); }); renderPlaylist(); if(currentIdx<0 && playlist.length>0) loadTrack(0); this.value=''; }; // Keyboard shortcuts when music tab is active document.addEventListener('keydown',(e)=>{ const musicActive=document.getElementById('panel-music').classList.contains('active'); if(!musicActive || e.target.tagName==='INPUT' || e.target.tagName==='TEXTAREA') return; if(e.code==='Space'&&!e.ctrlKey){ e.preventDefault(); togglePlay(); } }); })(); /* ═══════════ MVPs TV — Live Match Browser ═══════════ */ window.MvpsTv=(function(){ let sock=null; function escH(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]);} function getSocket(){ if(!sock&&typeof io!=='undefined') sock=io(); return sock; } function cardHTML(m){ return `
${escH(m.p1)} vs ${escH(m.p2)}
${escH((m.game||'').toUpperCase())} ${m.spectators||0} watching
`; } function renderInto(elId,matches){ const el=document.getElementById(elId); if(!el) return; if(!matches||!matches.length){ el.innerHTML='
No live matches right now
'; } else { el.innerHTML=matches.map(cardHTML).join(''); } } function fetchAndRender(targets){ const s=getSocket(); if(!s){ targets.forEach(id=>{const e=document.getElementById(id);if(e)e.innerHTML='
Server offline
';}); return; } s.emit('list-live',{game:'fighter'},(res)=>{ if(res&&res.ok) targets.forEach(id=>renderInto(id,res.matches)); }); } // Called when MVPSTV sidebar tab opens or Live view in tourneys is opened function refresh(){ fetchAndRender(['mvps-tv-match-list','live-panel-match-list','tourney-live-match-list']); } // Called when swiped to page 4 (live panel) function refreshPanel(){ fetchAndRender(['live-panel-match-list','mvps-tv-match-list','tourney-live-match-list']); } function watch(code,p1,p2){ launchGame('Fighting Game/index.html?spectate='+encodeURIComponent(code),'SPECTATING: '+p1+' vs '+p2,'FGT'); } document.addEventListener('DOMContentLoaded',()=>{ const btn=document.getElementById('mvps-tv-refresh'); if(btn) btn.addEventListener('click',refresh); const btn2=document.getElementById('live-panel-refresh'); if(btn2) btn2.addEventListener('click',refreshPanel); const btn3=document.getElementById('tourney-live-refresh'); if(btn3) btn3.addEventListener('click',refresh); }); return {refresh,refreshPanel,watch}; })(); /* ═══════════ Leaderboard ═══════════ */ window.Leaderboard=(function(){ const GAME_DISPLAY_NAMES={'Connect Four':'FourLine Clash','Othello':'Shadow Disks','Minesweeper':'Minefield Sweep'}; const displayGameName=name=>GAME_DISPLAY_NAMES[name]||name; const GAME_LABELS={'Chess':'CHESS','Checkers':'CHECKERS','Fighting Game':'FIGHTING', 'FourLine Clash':'FOURLINE CLASH','Spades':'SPADES','Hearts':'HEARTS','Solitaire':'SOLITAIRE','Neon Stack':'NEON STACK','Sudoku':'SUDOKU','Tic Tac Toe':'TTT','Fleet Fall':'FLEET FALL','Shadow Disks':'SHADOW DISKS','Word Scramble':'WORD SCRAMBLE','Minefield Sweep':'MINEFIELD SWEEP','Snake':'SNAKE','Trivia Tycoon':'TRIVIA TYCOON','Case Cracker':'CASE CRACKER'}; const ICON_TO_GAME={'CHE':'Chess','CHK':'Checkers','C-4':'FourLine Clash','ttt':'Tic Tac Toe', 'NSK':'Neon Stack','SDK':'Sudoku','SPA':'Spades','HRT':'Hearts','SOL':'Solitaire','FGT':'Fighting Game','FFL':'Fleet Fall','OTH':'Shadow Disks','WRD':'Word Scramble','MSW':'Minefield Sweep','SNK':'Snake','MIL':'Trivia Tycoon','DND':'Case Cracker','NBA':'MVP Hoopers','MON':'Monster Gamers'}; let sock=null; let filters={game:'',period:'all',mode:''}; function getSocket(){ if(!sock&&typeof io!=='undefined') sock=io(); return sock; } function esc(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]);} function rowHTML(entry,rank,showGame){ const cls=rank===1?'top1':rank===2?'top2':rank===3?'top3':''; const medal=rank===1?'🥇':rank===2?'🥈':rank===3?'🥉':rank; return `
${medal}
${esc(entry.name)} ${showGame&&entry.game?`${esc(GAME_LABELS[entry.game]||entry.game)}`:''}
${entry.score} W
`; } function render(res){ const list=document.getElementById('lb-list'); if(!list) return; let rows=[]; if(filters.game){ // server returned { entries: [...] } rows=(res.entries||[]).map((e,i)=>({...e,game:filters.game})); } else { // server returned { all: { game: [...] } } for(const [game,entries] of Object.entries(res.all||{})){ entries.forEach(e=>rows.push({...e,game})); } rows.sort((a,b)=>b.score-a.score); rows=rows.slice(0,20); } if(!rows.length){list.innerHTML='
No wins recorded yet — play some games!
';return;} list.innerHTML=rows.map((e,i)=>rowHTML(e,i+1,!filters.game)).join(''); } function fetch(){ const s=getSocket(); if(!s){const el=document.getElementById('lb-list');if(el)el.innerHTML='
Server offline
';return;} const params={period:filters.period,mode:filters.mode||undefined}; if(filters.game) params.game=filters.game; s.emit('get-leaderboard',params,(res)=>{if(res&&res.ok) render(res);}); } function submitScore(icon,mode){ const game=ICON_TO_GAME[icon]; if(!game) return; const s=getSocket(); if(!s) return; const name=getPlayerData().name||platformDisplayName(); // submit 1 win per play recorded s.emit('submit-score',{game,name,score:1,mode:mode||''}); } function onFilterChange(){ filters.game=document.getElementById('lb-select-game')?.value||''; filters.period=document.getElementById('lb-select-period')?.value||'all'; filters.mode=document.getElementById('lb-select-mode')?.value||''; fetch(); } function isLeaderboardVisible(){ const panel=document.getElementById('leaderboard-panel'); return panel&&panel.offsetParent!==null; } document.addEventListener('DOMContentLoaded',()=>{ document.getElementById('lb-refresh-btn')?.addEventListener('click',fetch); ['lb-select-game','lb-select-period','lb-select-mode'].forEach(id=>{ document.getElementById(id)?.addEventListener('change',onFilterChange); }); const s=getSocket(); if(s) s.on('leaderboard:updated',()=>{ if(isLeaderboardVisible()) fetch(); }); }); return {fetch,submitScore}; })(); /* ═══════════ Leaders Sub-Tab Switching ═══════════ */ (function(){ document.addEventListener('DOMContentLoaded',function(){ document.querySelectorAll('.leaders-subtab').forEach(function(btn){ btn.addEventListener('click',function(){ document.querySelectorAll('.leaders-subtab').forEach(function(b){b.classList.remove('active');}); document.querySelectorAll('.leaders-pane').forEach(function(p){p.classList.remove('active');}); btn.classList.add('active'); var pane=document.getElementById(btn.getAttribute('data-pane')); if(pane) pane.classList.add('active'); if(btn.getAttribute('data-pane')==='leaders-pane-ps' && window.PersonalStats) PersonalStats.refresh(); if(btn.getAttribute('data-pane')==='leaders-pane-lb' && window.Leaderboard) Leaderboard.fetch(); }); }); }); })(); /* ═══════════ Personal Stats (reads from localStorage DC data) ═══════════ */ window.PersonalStats=(function(){ 'use strict'; /* ── DC prefix map (game display name → localStorage prefix) ── */ var DC_MAP={ 'Chess':'chess','Checkers':'checkers','FourLine Clash':'c4', 'Fleet Fall':'fleetfall','Fighting Game':'fighting','Minefield Sweep':'minesweeper', 'Sudoku':'sudoku','Snake':'snake','Neon Stack':'neonstack','Solitaire':'solitaire', 'Hearts':'hearts', 'Shadow Disks':'othello','Monster Gamers':'monster','Case Cracker':'casecracker', 'Spades':'spades','Word Scramble':'ws','Trivia Tycoon':'trivia', 'Tic Tac Toe':'ttt' }; var RANKING_EXCLUDED_GAMES={ 'Trivia Tycoon':true }; function isRankingExcludedGame(name){ return !!RANKING_EXCLUDED_GAMES[name]; } var ALL_GAMES=Object.keys(DC_MAP); var _filterGame='', _filterResult='', _filterContext='', _filterDiff=''; function esc(s){return String(s).replace(/[&<>"']/g,function(c){return{'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];});} function timeAgo(ts){ var diff=Date.now()-ts; if(diff<60000) return 'just now'; if(diff<3600000) return Math.floor(diff/60000)+'m ago'; if(diff<86400000) return Math.floor(diff/3600000)+'h ago'; var days=Math.floor(diff/86400000); return days<30 ? days+'d ago' : Math.floor(days/30)+'mo ago'; } function fmtDur(sec){ if(!sec || sec<=0) return ''; var m=Math.floor(sec/60), s=sec%60; return m>0 ? m+'m '+s+'s' : s+'s'; } function rankTier(e){ if(e>=2200) return {t:'Legend',b:'\ud83d\udc51',c:'#f5c518'}; if(e>=2000) return {t:'Grandmaster',b:'\ud83d\udd34',c:'#ef4444'}; if(e>=1800) return {t:'Master',b:'\ud83d\udfe0',c:'#f97316'}; if(e>=1600) return {t:'Expert',b:'\ud83d\udfe1',c:'#eab308'}; if(e>=1400) return {t:'Skilled',b:'\ud83d\udfe3',c:'#a855f7'}; if(e>=1200) return {t:'Competitor',b:'\ud83d\udd35',c:'#3b82f6'}; if(e>=1000) return {t:'Apprentice',b:'\ud83d\udfe2',c:'#22c55e'}; if(e>=800) return {t:'Novice',b:'\u26aa',c:'#d1d5db'}; return {t:'Beginner',b:'\ud83d\udfe4',c:'#92400e'}; } function resultIcon(r){ if(r==='win') return '\u2705'; if(r==='loss') return '\u274c'; if(r==='draw'||r==='neutral') return '\u2796'; return '\u2753'; } function oppLabel(opp){ if(!opp) return ''; var t=opp.type||'cpu'; var icon=t==='online-human'?'\ud83c\udf10':t==='local-human'?'\ud83d\udc64':'\ud83e\udd16'; return icon+' '+(opp.name||t); } /* ── Gather all DC data from localStorage ── */ function gatherData(){ var perGame={}, allHistory=[], totW=0, totL=0, totD=0, totG=0, bestElo=0; ALL_GAMES.forEach(function(game){ var pfx=DC_MAP[game]; var history=[]; try{ history=JSON.parse(localStorage.getItem(pfx+'_match_history')||'[]'); }catch(e){} var elo=parseInt(localStorage.getItem(pfx+'_elo_rating')||'0',10); var w=0,l=0,d=0,totalDur=0; history.forEach(function(h){ if(h.result==='win') w++; else if(h.result==='loss') l++; else d++; totalDur+=(h.duration||0); allHistory.push(Object.assign({_game:game},h)); }); var total=w+l+d; var rankedGame=!isRankingExcludedGame(game); perGame[game]={wins:w,losses:l,draws:d,total:total,elo:rankedGame?elo:0, avgDur:total>0?Math.round(totalDur/total):0, winRate:total>0?Math.round((w/total)*100):0, historyOnly:!rankedGame}; if(rankedGame){ totW+=w; totL+=l; totD+=d; totG+=total; if(elo>bestElo) bestElo=elo; } }); allHistory.sort(function(a,b){return (b.ts||0)-(a.ts||0);}); return {perGame:perGame,allHistory:allHistory,totW:totW,totL:totL,totD:totD,totG:totG,bestElo:bestElo}; } /* ── Render summary card ── */ function renderSummary(data,name){ var nameEl=document.getElementById('ps-player-name'); if(nameEl) nameEl.textContent=name; var row=document.getElementById('ps-summary-row'); if(!row) return; var rk=data.bestElo>0?rankTier(data.bestElo):{t:'Unranked',b:'',c:'#666'}; var rate=data.totG>0?Math.round((data.totW/data.totG)*100):0; var h=''; h+=''+data.totG+' PLAYED'; h+=''+data.totW+' W'; h+=''+data.totL+' L'; if(data.totD>0) h+=''+data.totD+' D'; h+=''+rate+'% WIN'; if(data.bestElo>0) h+=''+rk.b+' '+data.bestElo+' '+rk.t+''; row.innerHTML=h; } /* ── Render per-game grid ── */ function renderGrid(data){ var gridEl=document.getElementById('ps-game-grid'); if(!gridEl) return; var html=''; ALL_GAMES.forEach(function(g){ var s=data.perGame[g]; var total=s.total, w=s.wins, l=s.losses, d=s.draws; var wPct=total>0?Math.round((w/total)*100):0; var lPct=total>0?Math.round((l/total)*100):0; var dPct=total>0?100-wPct-lPct:0; var rk=!s.historyOnly&&s.elo>0?rankTier(s.elo):null; html+='
'; html+='
'; html+=''+esc(g)+''; if(s.historyOnly) html+='📜 History'; else if(rk) html+=''+rk.b+' '+s.elo+''; html+='
'; html+='
'; html+=''+w+'W'; html+=''+l+'L'; if(d>0) html+=''+d+'D'; html+='
'; html+='
'; if(wPct>0) html+='
'; if(lPct>0) html+='
'; if(dPct>0) html+='
'; html+='
'; html+='
'; }); gridEl.innerHTML=html||'
No stats yet \u2014 play some games!
'; /* Attach click handlers */ gridEl.querySelectorAll('.ps-game-card[data-game]').forEach(function(card){ card.addEventListener('click',function(){ var g=card.getAttribute('data-game'); if(g) openDetail(g); }); }); } /* ══════════════════════════════════════════════════════ GAME DETAIL DRILL-DOWN ══════════════════════════════════════════════════════ */ /* Per-game extras label map — maps raw extras keys to human-readable labels */ var EXTRAS_LABELS={ /* Chess */ opening:'Opening',notation:'Notation',captures:'Captures', /* Checkers */ kingsFormed:'Kings Formed',jumpsTotal:'Total Jumps',grade:'Grade', /* FourLine Clash */longestChain:'Longest Chain', /* Fleet Fall */ shotsTotal:'Total Shots',shotsMissed:'Shots Missed',shipsLost:'Ships Lost', /* Fighting */ p1:'Player 1',p2:'Player 2',rounds:'Rounds',isKO:'Knockouts', /* Minefield Sweep */ gridSize:'Grid Size',minesTotal:'Total Mines',flagsUsed:'Flags Used',cellsRevealed:'Cells Revealed', /* Sudoku */ errors:'Errors',hints:'Hints Used', /* Snake */ detail:'Detail', /* Solitaire */ undos:'Undos',efficiency:'Efficiency', /* Neon Stack */ level:'Level',lines:'Lines Cleared', /* Shadow Disks */ finalBlack:'Final Black',finalWhite:'Final White', /* Monster */ enemyLevel:'Enemy Level',coins:'Coins',crystals:'Crystals', /* Case Cracker */type:'Type', /* Word Scramble */wordsFound:'Words Found',totalAvailable:'Words Available',streak:'Streak', /* Trivia */ correct:'Correct',lifelinesUsed:'Lifelines',fastest:'Fastest (s)', /* Spades */ moonShot:'Moon Shots',cutthroat:'Cutthroat', /* TTT */ gradeX:'Grade (X)',gradeO:'Grade (O)',rules:'Rules Mode' }; /* Keys to skip in extras (already displayed elsewhere or too long) */ var EXTRAS_SKIP={notation:1,detail:1}; /* Aggregate extras: average numbers, count booleans, mode for strings */ function aggregateExtras(history){ var sums={},counts={},types={},strCounts={}; history.forEach(function(h){ var ex=h.extras||{}; for(var k in ex){ if(!ex.hasOwnProperty(k)||EXTRAS_SKIP[k]) continue; var v=ex[k]; if(typeof v==='number'){ sums[k]=(sums[k]||0)+v; counts[k]=(counts[k]||0)+1; types[k]='number'; } else if(typeof v==='boolean'){ sums[k]=(sums[k]||0)+(v?1:0); counts[k]=(counts[k]||0)+1; types[k]='boolean'; } else if(typeof v==='string' && v.length<30){ if(!strCounts[k]) strCounts[k]={}; strCounts[k][v]=(strCounts[k][v]||0)+1; counts[k]=(counts[k]||0)+1; types[k]='string'; } } }); var result=[]; for(var k in counts){ var lbl=EXTRAS_LABELS[k]||k.replace(/([A-Z])/g,' $1').replace(/^./,function(c){return c.toUpperCase();}); if(types[k]==='number'){ var avg=counts[k]>0?sums[k]/counts[k]:0; result.push({label:lbl,value:avg%1===0?String(avg):avg.toFixed(1),raw:avg}); } else if(types[k]==='boolean'){ result.push({label:lbl,value:sums[k]+'/'+counts[k],raw:sums[k]}); } else if(types[k]==='string'){ var best='',bestN=0; for(var sv in strCounts[k]){if(strCounts[k][sv]>bestN){bestN=strCounts[k][sv];best=sv;}} result.push({label:lbl,value:best,raw:bestN}); } } return result; } /* Compute streaks */ function computeStreaks(history){ var cur=0,curType='',best=0; for(var i=history.length-1;i>=0;i--){ var r=history[i].result; if(r===curType){cur++;}else{cur=1;curType=r;} if(curType==='win'&&cur>best) best=cur; } /* Current streak (most recent) */ var cs=0,csType=''; for(var j=0;jbestN){bestN=mc[m];best=m;}} return best||'—'; } /* Find most common opponent */ function topOpponent(history){ var oc={}; history.forEach(function(h){ var n=h.opponent?h.opponent.name:''; if(n) oc[n]=(oc[n]||0)+1; }); var best='',bestN=0; for(var n in oc){if(oc[n]>bestN){bestN=oc[n];best=n;}} return best||'—'; } /* Draw SVG sparkline for ELO history */ function drawSparkline(container,eloHistory){ if(!eloHistory||eloHistory.length<2){ container.innerHTML='
Not enough ELO data yet
'; return; } var W=container.clientWidth||260, H=48; var pts=eloHistory; var min=Infinity,max=-Infinity; pts.forEach(function(p){if(p.elomax)max=p.elo;}); if(min===max){min-=50;max+=50;} var pad=4,gW=W-pad*2,gH=H-pad*2; var coords=pts.map(function(p,i){ var x=pad+(i/(pts.length-1))*gW; var y=pad+gH-(((p.elo-min)/(max-min))*gH); return {x:x,y:y,elo:p.elo}; }); var pathD=coords.map(function(c,i){return(i===0?'M':'L')+c.x.toFixed(1)+','+c.y.toFixed(1);}).join(' '); var last=coords[coords.length-1]; var first=coords[0]; var trend=last.elo>=first.elo; var color=trend?'#00e676':'#ef4444'; var svg=''; /* Reference line at 1200 */ if(min<=1200&&max>=1200){ var ry=pad+gH-(((1200-min)/(max-min))*gH); svg+=''; } /* Gradient fill under line */ svg+=''; var fillD=pathD+' L'+last.x.toFixed(1)+','+H+' L'+first.x.toFixed(1)+','+H+' Z'; svg+=''; svg+=''; /* End dot */ svg+=''; /* Labels */ svg+=''+min+''; svg+=''+max+''; svg+=''; container.innerHTML=svg; } function openDetail(game){ var gridEl=document.getElementById('ps-game-grid'); var detEl=document.getElementById('ps-detail'); if(!gridEl||!detEl) return; gridEl.style.display='none'; detEl.classList.add('active'); var pfx=DC_MAP[game]; if(!pfx){closeDetail();return;} var history=[]; try{history=JSON.parse(localStorage.getItem(pfx+'_match_history')||'[]');}catch(e){} var elo=parseInt(localStorage.getItem(pfx+'_elo_rating')||'0',10); var eloHist=[]; try{eloHist=JSON.parse(localStorage.getItem(pfx+'_elo_history')||'[]');}catch(e){} var w=0,l=0,d=0,totalDur=0,totalScore=0,totalOppScore=0; history.forEach(function(h){ if(h.result==='win') w++; else if(h.result==='loss') l++; else d++; totalDur+=(h.duration||0); if(h.score){totalScore+=(h.score.player||0);totalOppScore+=(h.score.opponent||0);} }); var total=w+l+d; var rate=total>0?Math.round((w/total)*100):0; var avgDur=total>0?Math.round(totalDur/total):0; var streaks=computeStreaks(history); var favMode=favoriteMode(history); var topOpp=topOpponent(history); var rk=!isRankingExcludedGame(game)&&elo>0?rankTier(elo):null; var extras=aggregateExtras(history); var lastPlayed=history.length>0?timeAgo(history[0].ts||0):'Never'; var h=''; /* Back button */ h+=''; /* Header */ h+='
'; h+=''+esc(game)+''; if(rk) h+=''+rk.b+' '+elo+' '+rk.t+''; h+='
'; /* Core stat cards — row 1 */ h+='
'; h+='
'+total+'Games
'; h+='
'+w+'Wins
'; h+='
'+l+'Losses
'; h+='
'+d+'Draws
'; h+='
'+rate+'%Win Rate
'; h+='
'+fmtDur(avgDur)||'\u2014'+'Avg Duration
'; h+='
'; /* Row 2 — streaks & context */ h+='
'; var csIcon=streaks.currentType==='win'?'\u2705':streaks.currentType==='loss'?'\u274c':'\u2796'; h+='
'+streaks.current+' '+csIcon+'Current Streak
'; h+='
'+streaks.best+'Best Win Streak
'; h+='
'+esc(favMode)+'Favorite Mode
'; h+='
'+esc(topOpp)+'Top Opponent
'; h+='
'+lastPlayed+'Last Played
'; if(total>0){ h+='
'+totalScore+'-'+totalOppScore+'Total Score
'; } h+='
'; /* ELO sparkline */ if(eloHist.length>=2){ h+='
'; h+='
ELO Progression
'; h+='
'; h+='
'; } /* Game-specific extras */ if(extras.length>0){ h+='
'; h+='
Game Stats (Averages)
'; h+='
'; extras.forEach(function(ex){ h+='
'; h+=''+esc(String(ex.value))+''; h+=''+esc(ex.label)+''; h+='
'; }); h+='
'; } /* Recent matches */ if(history.length>0){ h+='
'; h+='
Recent Matches ('+Math.min(history.length,15)+')
'; var recent=history.slice(0,15); /* Find ELO deltas for these matches */ recent.forEach(function(m){ var icon=resultIcon(m.result); var opp=oppLabel(m.opponent); var sc=''; if(m.score&&(m.score.player!=null||m.score.opponent!=null)) sc=(m.score.player||0)+'\u2013'+(m.score.opponent||0); var dur=fmtDur(m.duration); /* ELO delta from elo_history */ var delta=null; for(var ei=eloHist.length-1;ei>=1;ei--){ if(Math.abs((eloHist[ei].ts||0)-(m.ts||0))<2000){delta=eloHist[ei].elo-eloHist[ei-1].elo;break;} } h+='
'; h+=''+icon+''; if(opp) h+='vs'+esc(opp)+''; if(sc) h+=''+sc+''; if(dur) h+=''+dur+''; if(m.mode) h+=''+esc(m.mode)+''; if(delta!==null){ if(delta>0) h+='+'+delta+''; else if(delta<0) h+=''+delta+''; else h+='\u00b10'; } h+=''+timeAgo(m.ts||0)+''; h+='
'; }); } detEl.innerHTML=h; /* Back button handler */ document.getElementById('ps-detail-back').addEventListener('click',closeDetail); /* Draw sparkline after DOM insertion */ if(eloHist.length>=2){ var canvas=document.getElementById('ps-spark-canvas'); if(canvas) drawSparkline(canvas,eloHist); } } function closeDetail(){ var gridEl=document.getElementById('ps-game-grid'); var detEl=document.getElementById('ps-detail'); if(gridEl) gridEl.style.display=''; if(detEl){detEl.classList.remove('active');detEl.innerHTML='';} } /* ── Render match history feed (expandable cards) ── */ function renderHistory(data){ var histEl=document.getElementById('ps-history'); if(!histEl) return; var list=data.allHistory; /* Apply filters */ if(_filterGame) list=list.filter(function(h){return h._game===_filterGame;}); if(_filterResult) list=list.filter(function(h){return h.result===_filterResult;}); if(_filterContext) list=list.filter(function(h){return (h.context||'local')===_filterContext;}); if(_filterDiff) list=list.filter(function(h){return (h.difficulty||'').toLowerCase()===_filterDiff;}); list=list.slice(0,200); if(!list.length){ histEl.innerHTML='
No matches found
'; return; } /* Build ELO snapshot map */ var eloSnaps={}; ALL_GAMES.forEach(function(g){ try{ eloSnaps[g]=JSON.parse(localStorage.getItem(DC_MAP[g]+'_elo_history')||'[]'); }catch(e){ eloSnaps[g]=[]; } }); function findEloDelta(game,ts){ if(isRankingExcludedGame(game)) return null; var snaps=eloSnaps[game]||[]; for(var i=snaps.length-1;i>=1;i--){ if(Math.abs((snaps[i].ts||0)-ts)<2000) return snaps[i].elo-snaps[i-1].elo; } return null; } var ctxIcon={online:'\uD83C\uDF10',local:'\uD83C\uDFE0'}; var oppTypeIcon={ai:'\uD83E\uDD16',human:'\uD83D\uDC64',online:'\uD83C\uDF10'}; var html=''; list.forEach(function(h){ var icon=resultIcon(h.result); var opp=oppLabel(h.opponent); var oppType=(h.opponent&&h.opponent.type)||''; var sc=''; if(h.score&&(h.score.player!=null||h.score.opponent!=null)){ sc=(h.score.player||0)+'\u2013'+(h.score.opponent||0); } var dur=fmtDur(h.duration); var delta=findEloDelta(h._game,h.ts); var deltaStr=''; if(delta!==null){ if(delta>0) deltaStr='+'+delta+''; else if(delta<0) deltaStr=''+delta+''; else deltaStr='\u00b10'; } var rcls='hc-'+h.result; html+='
'; /* ─ Summary row ─ */ html+='
'; html+=''+icon+''; html+=''+esc(h._game)+''; if(opp){ html+='vs'+esc(opp)+''; if(oppType) html+=''+(oppTypeIcon[oppType]||'')+''; } if(sc) html+=''+sc+''; if(dur) html+=''+dur+''; if(deltaStr) html+=deltaStr; html+=''+timeAgo(h.ts||0)+''; html+='\u25BC'; html+='
'; /* ─ Detail section ─ */ html+='
'; html+='
'; /* Mode */ html+='
Mode'+(h.mode?esc(h.mode):'\u2014')+'
'; /* Difficulty */ html+='
Difficulty'+(h.difficulty?esc(h.difficulty):'\u2014')+'
'; /* Context */ var ctx=h.context||'local'; html+='
Context'+(ctxIcon[ctx]||'')+' '+esc(ctx)+'
'; /* Ranked */ html+='
Ranked'+(h.ranked?'\u2705 Yes':'\u274C No')+'
'; /* Moves */ html+='
Moves'+(h.moves!=null?h.moves:'\u2014')+'
'; /* Score detail */ if(h.score&&(h.score.player!=null||h.score.opponent!=null)){ html+='
Score'+(h.score.player||0)+' \u2013 '+(h.score.opponent||0)+'
'; } /* Duration */ html+='
Duration'+(dur||'\u2014')+'
'; /* Opponent full */ if(h.opponent){ html+='
Opponent'+esc(h.opponent.name||'Unknown')+' ('+(h.opponent.type||'?')+')
'; } /* Match ID */ html+='
Match ID'+(h.id?(h.id.substring(0,8)):'\u2014')+'
'; /* Full timestamp */ html+='
Played'+(h.ts?new Date(h.ts).toLocaleString():'\u2014')+'
'; html+='
'; /* /ps-hd-grid */ /* Extras */ var ex=h.extras; if(ex&&typeof ex==="object"){ var keys=Object.keys(ex).filter(function(k){return !EXTRAS_SKIP[k];}); if(keys.length){ html+='
Game-Specific Stats
'; html+='
'; keys.forEach(function(k){ var lbl=EXTRAS_LABELS[k]||k.replace(/([A-Z])/g,' $1').replace(/^./,function(c){return c.toUpperCase();}); var val=ex[k]; if(typeof val==="boolean") val=val?'Yes':'No'; else if(typeof val==="number") val=val%1===0?val:val.toFixed(2); else val=String(val); html+='
'+esc(lbl)+''+esc(val)+'
'; }); html+='
'; } } html+='
'; /* /ps-hcard-detail */ html+='
'; /* /ps-hcard */ }); histEl.innerHTML=html; /* Wire expand/collapse */ histEl.querySelectorAll('.ps-hcard-summary').forEach(function(sum){ sum.addEventListener('click',function(){ var card=sum.parentElement; card.classList.toggle('expanded'); }); }); } /* ── Populate filter dropdown ── */ function populateFilters(){ var sel=document.getElementById('ps-filter-game'); if(!sel||sel.options.length>1) return; ALL_GAMES.forEach(function(g){ var o=document.createElement('option'); o.value=g; o.textContent=g; sel.appendChild(o); }); } /* ── Main refresh ── */ function refresh(){ var name=(typeof getPlayerData==='function'?getPlayerData().name:null)||platformDisplayName(); var data=gatherData(); renderSummary(data,name); renderGrid(data); populateFilters(); renderHistory(data); } /* ── Section tab switching + filter listeners ── */ document.addEventListener('DOMContentLoaded',function(){ document.querySelectorAll('.ps-section-tab').forEach(function(btn){ btn.addEventListener('click',function(){ closeDetail(); /* close any open game detail drill-down */ document.querySelectorAll('.ps-section-tab').forEach(function(b){b.classList.remove('active');}); document.querySelectorAll('.ps-section-content').forEach(function(p){p.classList.remove('active');}); btn.classList.add('active'); var sec=document.getElementById(btn.getAttribute('data-section')); if(sec) sec.classList.add('active'); }); }); var filterGame=document.getElementById('ps-filter-game'); var filterResult=document.getElementById('ps-filter-result'); var filterContext=document.getElementById('ps-filter-context'); var filterDiff=document.getElementById('ps-filter-diff'); if(filterGame) filterGame.addEventListener('change',function(){_filterGame=this.value;refresh();}); if(filterResult) filterResult.addEventListener('change',function(){_filterResult=this.value;refresh();}); if(filterContext) filterContext.addEventListener('change',function(){_filterContext=this.value;refresh();}); if(filterDiff) filterDiff.addEventListener('change',function(){_filterDiff=this.value;refresh();}); }); return {refresh:refresh}; })(); /* ═══════════ Active-game state tracking ═══════════ */ var _activeGame=null; // {url,title,icon,resolvedUrl} var _switchGameCb=null; // pending callback for switch confirm var _launchToken=0; // bumps on every intentional launch; cancels stale loads/resumes var _userChoseGame=false; // true after any click/launch this page life — blocks auto-resume function _setGameFrameLoading(on){ if(!gamePanEl) return; gamePanEl.classList.toggle('is-loading', !!on); } function _frameSrcPath(src){ try{ if(!src || src==='about:blank') return ''; return new URL(src, location.origin).pathname.replace(/^\/+/,''); }catch(e){ return ''; } } /* ── Refresh-resume: relaunch the game the user was in before refresh ── */ (function(){ /* Listen for iframe games telling us the game ended (clear active-game) */ window.addEventListener('message',function(ev){ if(ev.data && ev.data.type==='arcade-clear-active-game'){ try{sessionStorage.removeItem('arcade_active_game');}catch(e){} } }); /* On page load, check if a game was active before refresh */ function _tryResumeSavedGame(){ var raw; try{raw=sessionStorage.getItem('arcade_active_game');}catch(e){return;} if(!raw) return; var saved; try{saved=JSON.parse(raw);}catch(e){sessionStorage.removeItem('arcade_active_game');return;} if(!saved||!saved.url) return; /* Small delay to let the arcade shell finish initializing */ setTimeout(function(){ /* Never steal focus if the user already picked a different game */ if(_userChoseGame || _activeGame) return; if(typeof launchGame==='function') launchGame(saved.url,saved.title||'',saved.icon||'',{fromAutoResume:true}); },120); } if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',_tryResumeSavedGame); else _tryResumeSavedGame(); })(); function _getGameIconHtml(icon){ /* Return the MINI_ICON_MAP logo for a game icon code, or a fallback */ var map=window._MINI_ICON_MAP||{}; return map[icon]||(''+(icon?'\uD83C\uDFAE':'\uD83C\uDFAE')+''); } function updateResumeCard(){ var card=document.getElementById('resume-card'); if(!card) return; if(!_activeGame || gamePanEl.classList.contains('active')){ card.classList.remove('visible'); return; } card.classList.add('visible'); var iconEl=document.getElementById('resume-card-icon'); var titleEl=document.getElementById('resume-card-title'); if(iconEl) iconEl.innerHTML=_getGameIconHtml(_activeGame.icon); if(titleEl) titleEl.textContent=_activeGame.title||'Game'; } function resumeGame(){ if(!_activeGame) return; _userChoseGame=true; _setGameFrameLoading(false); gamePanEl.classList.add('active'); splitLayout.classList.add('game-active','sidebar-collapsed'); document.body.classList.add('in-game'); window.stopStars(); stopShowcase(); _aeuSyncGameFullscreen(true); _gpbSyncActionButtons(); try{mainFrame.contentWindow.focus();}catch(e){} updateResumeCard(); } function _teardownActiveGame(opts){ opts=opts||{}; var keepPaneActive=!!opts.keepPaneActive; var reportIdle=opts.reportIdle!==false; var blankFrame=opts.blankFrame!==false; mainFrame.onload=null; if(blankFrame){ try{mainFrame.removeAttribute('src');}catch(e){} mainFrame.src='about:blank'; } if(keepPaneActive){ gamePanEl.classList.add('active'); splitLayout.classList.add('game-active','sidebar-collapsed'); document.body.classList.add('in-game'); window.stopStars(); stopShowcase(); }else{ _setGameFrameLoading(false); _aeuExitGameFullscreen(); gamePanEl.classList.remove('active'); splitLayout.classList.remove('game-active','sidebar-collapsed'); document.body.classList.remove('in-game'); window.startStars(); startShowcase(); } // Report idle presence if(reportIdle && window.FriendSystem){ var fs=window.FriendSystem; if(fs._getSocket && fs._getMyName){ var s=fs._getSocket(); var n=fs._getMyName(); if(s&&n) s.emit('presence:idle',{name:n}); } } _activeGame=null; updateResumeCard(); _gpbSyncActionButtons(); } function endGame(){ try{sessionStorage.removeItem('arcade_active_game');}catch(e){} _teardownActiveGame({reportIdle:true}); } function _switchToGame(resolvedUrl,url,title,icon){ _setGameFrameLoading(true); _teardownActiveGame({keepPaneActive:true,reportIdle:false}); var launchNext=window.requestAnimationFrame||function(cb){setTimeout(cb,16);}; launchNext(function(){ _doLaunchGame(resolvedUrl,url,title,icon||''); }); } function _showSwitchConfirm(oldTitle,newTitle,onYes){ var overlay=document.getElementById('switch-game-overlay'); var msg=document.getElementById('switch-game-msg'); var yesBtn=document.getElementById('switch-game-yes'); var noBtn=document.getElementById('switch-game-no'); if(!overlay) { onYes(); return; } msg.innerHTML='You have '+(oldTitle||'a game')+' still running.
End it and start '+(newTitle||'the new game')+'?'; overlay.classList.add('open'); function cleanup(){ overlay.classList.remove('open'); yesBtn.onclick=null; noBtn.onclick=null; } yesBtn.onclick=function(){ cleanup(); onYes(); }; noBtn.onclick=function(){ cleanup(); }; overlay.onclick=function(e){ if(e.target===overlay){ cleanup(); } }; } function launchGame(url,title,icon,opts){ opts=opts||{}; if(!opts.fromAutoResume) _userChoseGame=true; var resolvedUrl=resolveGameUrl(url); // If same game is already loaded — just resume it if(_activeGame && _activeGame.resolvedUrl===resolvedUrl){ resumeGame(); return; } // If a different game is running — ask to switch if(_activeGame){ if(opts.fromAutoResume) return; // never auto-resume over a live choice _showSwitchConfirm(_activeGame.title, title, function(){ _switchToGame(resolvedUrl,url,title,icon||''); }); return; } _doLaunchGame(resolvedUrl,url,title,icon||''); } function _doLaunchGame(resolvedUrl,url,title,icon){ var token=++_launchToken; var targetPath=_frameSrcPath(resolvedUrl); var currentPath=_frameSrcPath(mainFrame && mainFrame.src); // Always hide the frame before any navigation so the previous title cannot paint _setGameFrameLoading(true); // Kill old game if any (blank the frame) if(_activeGame || (currentPath && currentPath!==targetPath)){ _teardownActiveGame({keepPaneActive:true,reportIdle:false}); }else{ mainFrame.onload=null; try{mainFrame.removeAttribute('src');}catch(e){} mainFrame.src='about:blank'; } _activeGame={url:url,title:title,icon:icon,resolvedUrl:resolvedUrl}; trackHubAnalytics('mode-select',{panel:'game-pane',game:title||'',url:url||''}); /* Persist active game so browser refresh relaunches it */ try{sessionStorage.setItem('arcade_active_game',JSON.stringify({url:url,title:title,icon:icon}));}catch(e){} gamePanEl.classList.add('active'); splitLayout.classList.add('game-active','sidebar-collapsed'); document.body.classList.add('in-game'); window.stopStars(); stopShowcase(); _aeuSyncGameFullscreen(true); _gpbSyncActionButtons(); updateResumeCard(); var finalUrl=resolvedUrl+(resolvedUrl.includes('?')?'&':'?')+'_t='+Date.now(); var launchNext=window.requestAnimationFrame||function(cb){setTimeout(cb,16);}; /* Two frames: let about:blank apply, then navigate — no stale paint */ launchNext(function(){ launchNext(function(){ if(token!==_launchToken) return; mainFrame.onload=function(){ if(token!==_launchToken) return; _setGameFrameLoading(false); try{mainFrame.contentWindow.focus();}catch(e){} if(window.ArcadeAuth) ArcadeAuth.syncFrame(); _aeuSyncGameFullscreen(false); setTimeout(_gpbSyncActionButtons,150); setTimeout(_gpbSyncActionButtons,900); /* Relay commentary from iframe's AEUHeader into the game-pane-bar ticker */ try{ var ifrWin=mainFrame.contentWindow; var origShow=ifrWin.AEUHeader&&ifrWin.AEUHeader.showCommentary; if(origShow){ ifrWin.AEUHeader.showCommentary=function(text,icn){ origShow.call(ifrWin.AEUHeader,text,icn); gpbShowCommentary(text,icn); }; } }catch(e){} }; mainFrame.src=finalUrl; }); }); } /* exitGame() is now an alias for going home (hides game, keeps it alive) */ function exitGame(){ goHome(); } function goPreviousPage(){ try{sessionStorage.removeItem('arcade_active_game');}catch(e){} _aeuExitGameFullscreen(); var referrer=''; var currentHref=''; try{referrer=String(document.referrer||'');}catch(e){} try{currentHref=String(window.location.href||'');}catch(e){} if(referrer&&referrer!==currentHref){ window.location.href=referrer; return; } if(window.history&&window.history.length>1){ _allowBrowserBackNavigation=true; window.setTimeout(function(){ _allowBrowserBackNavigation=false; }, 1000); if(window.history.length>2){ window.history.go(-2); return; } window.history.back(); return; } goHome({skipMatchConfirm:true}); } /* gameBack() — send a "go back one page" to the game iframe. If the game responds that it's already at its menu, leave the current page. If the game doesn't respond at all, also go home. */ var _gameBackTimer=null; var _gameBackHandled=false; var _gameBackPending=false; window.addEventListener('message',function(ev){ if(!ev.data||ev.data.type!=='arcade-back-response') return; if(!_gameBackPending) return; if(!gamePanEl||!gamePanEl.classList.contains('active')) return; _gameBackPending=false; _gameBackHandled=true; if(_gameBackTimer){clearTimeout(_gameBackTimer);_gameBackTimer=null;} if(ev.data.atMenu) goPreviousPage(); // else: game handled back internally }); window.addEventListener('message',function(ev){ if(!ev.data || ev.data.type!=='aeu-hub-back') return; if(gamePanEl && gamePanEl.classList.contains('active')) gameBack(); else forceHubTab('games'); }); window.addEventListener('message',function(ev){ if(!ev.data || ev.data.type!=='aeu-open-crm') return; if(window.ArcadeAuth && typeof ArcadeAuth.openCrm==='function') ArcadeAuth.openCrm(); }); function gameBack(){ if(!gamePanEl||!gamePanEl.classList.contains('active')) return; try{ var ifrWin=mainFrame&&mainFrame.contentWindow; if(ifrWin && typeof ifrWin.arcadeBack === 'function'){ var directResult=ifrWin.arcadeBack(); if(directResult && directResult.handled){ if(directResult.atMenu) goPreviousPage(); return; } } }catch(e){} _gameBackPending=true; _gameBackHandled=false; if(_gameBackTimer){clearTimeout(_gameBackTimer);_gameBackTimer=null;} try{ mainFrame.contentWindow.postMessage({type:'arcade-back'},'*'); }catch(e){ goHome(); return; } // Fallback: if game doesn't respond in 1200ms, go home _gameBackTimer=setTimeout(function(){ _gameBackTimer=null; if(!_gameBackHandled && gamePanEl.classList.contains('active')){ _gameBackPending=false; _completeGoHome(); } },1200); } /* ── Game-pane-bar action helpers ── */ function _gpbSyncFullscreenButton(){ var btn=document.getElementById('gpb-fullscreen-btn'); if(!btn) return; var supported=!!(document.documentElement&&(document.documentElement.requestFullscreen||document.documentElement.webkitRequestFullscreen||document.documentElement.msRequestFullscreen)); var active=!!_aeuIsGameFullscreenActive(); btn.style.display=(supported&&gamePanEl&&gamePanEl.classList.contains('active'))?'inline-flex':'none'; btn.classList.toggle('on',active); btn.textContent=active?'🗗 Exit Full Screen':'⛶ Full Screen'; btn.title=active?'Exit full screen':'Enter full screen'; btn.setAttribute('aria-label',active?'Exit full screen':'Enter full screen'); } function gpbToggleFullscreen(){ if(_aeuIsGameFullscreenActive()) _aeuExitGameFullscreen(); else _aeuRequestGameFullscreen(true); setTimeout(_gpbSyncFullscreenButton,0); } function _gpbSyncActionButtons(){ var tutBtn=document.getElementById('gpb-tutorial-btn'); var setBtn=document.getElementById('gpb-settings-btn'); if(!tutBtn||!setBtn) return; _gpbSyncFullscreenButton(); var showTut=false, showSet=false; try{ var ifrWin=mainFrame.contentWindow; var ifrDoc=ifrWin&&ifrWin.document; if(ifrWin&&ifrWin.AEUHeader&&typeof ifrWin.AEUHeader.refreshHeaderActions==='function'){ ifrWin.AEUHeader.refreshHeaderActions(); } var tutSrc=ifrDoc&&ifrDoc.getElementById('aeu-hdr-tutorial'); var setSrc=ifrDoc&&ifrDoc.getElementById('aeu-hdr-settings'); showTut=!!(tutSrc&&ifrWin.getComputedStyle(tutSrc).display!=='none'); showSet=!!(setSrc&&ifrWin.getComputedStyle(setSrc).display!=='none'); }catch(e){} tutBtn.style.display=showTut?'inline-flex':'none'; setBtn.style.display=showSet?'inline-flex':'none'; } function gpbOpenTutorial(){ try{ var ifrWin=mainFrame.contentWindow; if(ifrWin&&ifrWin.AEUHeader&&typeof ifrWin.AEUHeader.openGameTutorial==='function') ifrWin.AEUHeader.openGameTutorial(); }catch(e){} } function gpbOpenSettings(){ try{ var ifrWin=mainFrame.contentWindow; if(ifrWin&&ifrWin.AEUHeader&&typeof ifrWin.AEUHeader.openGameSettings==='function') ifrWin.AEUHeader.openGameSettings(); }catch(e){} } function gpbInvite(){ try{ var ifrWin=mainFrame.contentWindow; if(ifrWin&&ifrWin.AEUHeader) ifrWin.AEUHeader.openInvite(); }catch(e){} } var _gpbCommTimer=null; function gpbShowCommentary(text,icon){ var el=document.getElementById('gpb-commentary'); if(!el||!text) return; el.textContent=(icon||'\u{1F916}')+' '+text.replace(/^[\s\u200B]+|[\s\u200B]+$/g,''); el.classList.add('visible'); if(_gpbCommTimer) clearTimeout(_gpbCommTimer); _gpbCommTimer=setTimeout(function(){el.classList.remove('visible');},8000); } /* Sync friend/notif badge counts from iframe's AEUHeader or hub's own system */ function _gpbSyncBadges(){ var fbadge=document.getElementById('gpb-friend-badge'); var nbadge=document.getElementById('gpb-notif-badge'); // Use hub's FriendSystem pending count if(fbadge && window.FriendSystem && FriendSystem._getPendingCount){ var c=FriendSystem._getPendingCount(); fbadge.textContent=c>9?'9+':c; fbadge.style.display=c>0?'block':'none'; } // Use hub's ArcadeNotifs count if(nbadge && window.ArcadeNotifs && ArcadeNotifs.getCount){ var n=ArcadeNotifs.getCount(); nbadge.textContent=n>99?'99+':n; nbadge.style.display=n>0?'block':'none'; } } setInterval(_gpbSyncBadges,2000); setInterval(_gpbSyncActionButtons,1500); const installAppBtn=document.getElementById('install-app-btn'); const updateAppBtn=document.getElementById('update-app-btn'); const installAppPrompt=document.getElementById('install-app-prompt'); const installPromptTitle=document.getElementById('install-prompt-title'); const installPromptCopy=document.getElementById('install-prompt-copy'); const installPromptConfirm=document.getElementById('install-prompt-confirm'); const installPromptDismiss=document.getElementById('install-prompt-dismiss'); const iosInstallTip=document.getElementById('ios-install-tip'); const iosInstallClose=document.getElementById('ios-install-close'); let deferredInstallPrompt=null; let waitingServiceWorker=null; let reloadWhenControllerChanges=false; let installPromptMode='native'; let installPromptDismissed=false; function isStandaloneApp(){ return window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone===true; } function isIosDevice(){ return /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform==='MacIntel' && navigator.maxTouchPoints>1); } function getPreferredInstallMode(){ if(deferredInstallPrompt) return 'native'; if(isIosDevice()) return 'ios'; return 'manual'; } function showInstallButton(){} function hideInstallButton(){} function showInstallPrompt(mode='native'){ if(isStandaloneApp() || installPromptDismissed) return; installPromptMode=mode; if(mode==='ios'){ installPromptTitle.textContent='Install on your phone?'; installPromptCopy.innerHTML='Add AEU Super Arcade to your Home Screen: tap Share, then Add to Home Screen.'; installPromptConfirm.textContent='Show Steps'; }else if(mode==='manual'){ installPromptTitle.textContent='Install AEU Arcade?'; installPromptCopy.innerHTML=window.isSecureContext ? 'Open your browser menu and choose Install app or Add to Home Screen to save the arcade on your phone.' : 'This page is not in a secure context, so the browser may block the native install popup. Open the browser menu and use Add to Home Screen, or switch to HTTPS for the one-tap install prompt.'; installPromptConfirm.textContent='Got It'; }else{ installPromptTitle.textContent='Install AEU Arcade?'; installPromptCopy.textContent='Download the app on your phone for faster loading, full-screen play, and one-tap access.'; installPromptConfirm.textContent='Install'; } installAppPrompt.hidden=false; installAppPrompt.classList.add('visible'); } function hideInstallPrompt(){ installAppPrompt.hidden=true; installAppPrompt.classList.remove('visible'); } function showUpdateBanner(){ updateAppBtn.hidden=false; updateAppBtn.classList.add('visible'); } function hideUpdateBanner(){ updateAppBtn.hidden=true; updateAppBtn.classList.remove('visible'); } function showIosInstallTip(){ if(isStandaloneApp() || !isIosDevice() || deferredInstallPrompt) return; iosInstallTip.hidden=false; iosInstallTip.classList.add('visible'); } function hideIosInstallTip(){ iosInstallTip.hidden=true; iosInstallTip.classList.remove('visible'); } installAppBtn.addEventListener('click',async()=>{ installPromptDismissed=false; showInstallPrompt(getPreferredInstallMode()); }); updateAppBtn.addEventListener('click',()=>{ reloadWhenControllerChanges=true; hideUpdateBanner(); if(waitingServiceWorker){ waitingServiceWorker.postMessage({ type:'SKIP_WAITING' }); setTimeout(()=>location.reload(),1200); return; } location.reload(); }); iosInstallClose.addEventListener('click',hideIosInstallTip); installPromptDismiss.addEventListener('click',()=>{ hideInstallPrompt(); installPromptDismissed=true; }); installPromptConfirm.addEventListener('click',async()=>{ if(installPromptMode==='ios'){ hideInstallPrompt(); showIosInstallTip(); return; } if(installPromptMode==='manual'){ hideInstallPrompt(); return; } if(!deferredInstallPrompt) return; deferredInstallPrompt.prompt(); const choice=await deferredInstallPrompt.userChoice.catch(()=>null); deferredInstallPrompt=null; hideInstallPrompt(); hideInstallButton(); if(choice?.outcome!=='accepted'){ showInstallButton(); installPromptDismissed=true; if(isIosDevice() && !isStandaloneApp()) showIosInstallTip(); } }); window.addEventListener('beforeinstallprompt',event=>{ event.preventDefault(); deferredInstallPrompt=event; hideIosInstallTip(); showInstallButton(); installPromptDismissed=false; setTimeout(()=>showInstallPrompt('native'),500); }); window.addEventListener('appinstalled',()=>{ deferredInstallPrompt=null; installPromptDismissed=true; hideInstallPrompt(); hideInstallButton(); hideIosInstallTip(); }); if(!isStandaloneApp()){ setTimeout(()=>{ showInstallButton(); showInstallPrompt(getPreferredInstallMode()); },1200); } async function registerArcadeServiceWorker(){ if(!('serviceWorker' in navigator)) return; try{ const registration=await navigator.serviceWorker.register('/sw.js'); const handleReadyUpdate=worker=>{ waitingServiceWorker=registration.waiting || worker || navigator.serviceWorker.controller; showUpdateBanner(); }; if(registration.waiting) handleReadyUpdate(registration.waiting); registration.addEventListener('updatefound',()=>{ const installingWorker=registration.installing; if(!installingWorker) return; installingWorker.addEventListener('statechange',()=>{ if(installingWorker.state==='installed' && navigator.serviceWorker.controller){ handleReadyUpdate(installingWorker); } }); }); navigator.serviceWorker.addEventListener('controllerchange',()=>{ if(!reloadWhenControllerChanges) return; reloadWhenControllerChanges=false; location.reload(); }); setTimeout(()=>registration.update().catch(()=>{}),1500); }catch(err){ console.warn('[PWA] Service worker registration failed:',err); } } registerArcadeServiceWorker();