jumping
// ============================================================================ // MOCK DATA ENGINE (Replaces real API calls for Sandbox Environment) // ============================================================================ // Real AppIDs used so Akamai images work. const DB_GAMES = [ { appid: 730, name: "Counter-Strike 2", is_free: true, price: 0, mcc: 82, dev: "Valve", tags: ["FPS", "Shooter", "Multiplayer", "Competitive"] }, { appid: 570, name: "Dota 2", is_free: true, price: 0, mcc: 90, dev: "Valve", tags: ["MOBA", "Strategy", "Multiplayer", "e-sports"] }, { appid: 578080, name: "PUBG: BATTLEGROUNDS", is_free: true, price: 0, mcc: 86, dev: "KRAFTON, Inc.", tags: ["Survival", "Shooter", "Multiplayer", "Battle Royale"] }, { appid: 1172470, name: "Apex Legends", is_free: true, price: 0, mcc: 88, dev: "Respawn Entertainment", tags: ["Hero Shooter", "Battle Royale", "Multiplayer"] }, { appid: 271590, name: "Grand Theft Auto V", is_free: false, price: 2999, mcc: 96, dev: "Rockstar Games", tags: ["Open World", "Action", "Multiplayer", "Automobile Sim"] }, { appid: 1091500, name: "Cyberpunk 2077", is_free: false, price: 5999, mcc: 86, dev: "CD PROJEKT RED", tags: ["Cyberpunk", "Open World", "RPG", "Sci-fi"] }, { appid: 1245620, name: "ELDEN RING", is_free: false, price: 5999, mcc: 94, dev: "FromSoftware Inc.", tags: ["Souls-like", "Dark Fantasy", "RPG", "Open World"] }, { appid: 1086940, name: "Baldur's Gate 3", is_free: false, price: 5999, mcc: 96, dev: "Larian Studios", tags: ["RPG", "Story Rich", "Choices Matter", "Turn-Based Combat"] }, { appid: 413150, name: "Stardew Valley", is_free: false, price: 1499, mcc: 89, dev: "ConcernedApe", tags: ["Farming Sim", "Life Sim", "Pixel Graphics", "Co-op"] }, { appid: 105600, name: "Terraria", is_free: false, price: 999, mcc: 83, dev: "Re-Logic", tags: ["Survival", "Sandbox", "Adventure", "Pixel Graphics"] }, { appid: 252490, name: "Rust", is_free: false, price: 3999, mcc: 69, dev: "Facepunch Studios", tags: ["Survival", "Crafting", "Multiplayer", "Open World"] }, { appid: 230410, name: "Warframe", is_free: true, price: 0, mcc: 69, dev: "Digital Extremes", tags: ["Looter Shooter", "Action", "Sci-fi", "Free to Play"] }, { appid: 1142710, name: "Total War: WARHAMMER III", is_free: false, price: 5999, mcc: 85, dev: "CREATIVE ASSEMBLY", tags: ["Strategy", "Grand Strategy", "Fantasy", "RTS"] }, { appid: 292030, name: "The Witcher 3: Wild Hunt", is_free: false, price: 3999, mcc: 93, dev: "CD PROJEKT RED", tags: ["Open World", "RPG", "Story Rich", "Fantasy"] }, { appid: 400, name: "Portal", is_free: false, price: 999, mcc: 90, dev: "Valve", tags: ["Puzzle", "Sci-fi", "Singleplayer", "First-Person"] }, { appid: 620, name: "Portal 2", is_free: false, price: 999, mcc: 95, dev: "Valve", tags: ["Puzzle", "Co-op", "First-Person", "Comedy"] } ]; const MOCK_AVATARS = [ "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/avatars/fe/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_full.jpg", "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/avatars/00/00388d7dfb28492025ccaf6113c059725f4a6217_full.jpg", "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/avatars/24/24935f11ccfb7331ca118d052b65561a067f94bc_full.jpg", "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/avatars/41/41a547b7fb6f616e788ee52bb2d6a5c1044cf4c9_full.jpg" ]; // Seeded RNG for consistent fake data function sfc32(a, b, c, d) { return function() { a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0; let t = (a + b) | 0; a = b ^ b >>> 9; b = c + (c << 3) | 0; c = (c << 21 | c >>> 11); d = d + 1 | 0; t = t + d | 0; c = c + t | 0; return (t >>> 0) / 4294967296; } } function getSeededRandom(seedStr) { let seed = 0; for(let i=0; i new Promise(res => setTimeout(res, ms)); async function mockApiFetch(ep) { await delay(200 + Math.random() * 300); // 200-500ms delay // 1. GetMostPlayedGames if (ep.includes('GetMostPlayedGames')) { return { response: { ranks: DB_GAMES.slice(0, 10).map((g, i) => ({ appid: g.appid, name: g.name, concurrent_in_game: Math.floor(1000000 / Math.pow(1.5, i)), peak_in_game: Math.floor(1200000 / Math.pow(1.5, i)) })) } }; } // 2. ResolveVanityURL if (ep.includes('ResolveVanityURL')) { const url = new URLSearchParams(ep.split('?')[1]); const name = url.get('vanityurl') || "Player"; // Always resolve successfully to a fake ID derived from name let hash = 0; for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash); return { response: { success: 1, steamid: "76561198" + Math.abs(hash).toString().padStart(9, '0') } }; } // 3. GetPlayerSummaries if (ep.includes('GetPlayerSummaries')) { const url = new URLSearchParams(ep.split('?')[1]); const ids = url.get('steamids').split(','); const players = ids.map((id, index) => { const rng = getSeededRandom(id); const fakeName = "Player_" + id.slice(-4); return { steamid: id, personaname: index === 0 && id.includes('000') ? "SearchTarget" : fakeName, profileurl: `https://steamcommunity.com/profiles/${id}`, avatarfull: MOCK_AVATARS[Math.floor(rng() * MOCK_AVATARS.length)], personastate: Math.floor(rng() * 4), // 0-3 timecreated: 1300000000 + Math.floor(rng() * 200000000), loccountrycode: ["US", "GB", "DE", "FR", "JP", "KR"][Math.floor(rng() * 6)] }; }); return { response: { players } }; } // 4. GetOwnedGames if (ep.includes('GetOwnedGames')) { const url = new URLSearchParams(ep.split('?')[1]); const id = url.get('steamid'); const rng = getSeededRandom(id); const count = 5 + Math.floor(rng() * 10); // Shuffle games via seeded rng let shuffled = [...DB_GAMES].sort(() => rng() - 0.5); const games = shuffled.slice(0, count).map(g => { const played = rng() > 0.2; return { appid: g.appid, name: g.name, playtime_forever: played ? Math.floor(rng() * 10000) : 0, playtime_2weeks: played && rng() > 0.5 ? Math.floor(rng() * 1200) : 0 }; }); return { response: { game_count: games.length, games } }; } // 5. GetRecentlyPlayedGames if (ep.includes('GetRecentlyPlayedGames')) { // Generate subset of DB const games = DB_GAMES.slice(0, 3).map(g => ({ appid: g.appid, name: g.name, playtime_forever: 5000, playtime_2weeks: 500 })); return { response: { total_count: games.length, games } }; } // 6. GetSchemaForGame & GetPlayerAchievements if (ep.includes('GetSchemaForGame') || ep.includes('GetPlayerAchievements')) { return { game: { availableGameStats: { achievements: Array.from({length: 20}, (_,i) => ({ name: `ACH_${i}`, displayName: `Achievement ${i+1}`, description: `Complete task ${i+1} in the game.`, icon: `https://steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/${DB_GAMES[0].appid}/fake_icon.jpg` })) } }, playerstats: { achievements: Array.from({length: 10}, (_,i) => ({ apiname: `ACH_${i}`, achieved: 1, unlocktime: 1600000000 + i*10000 })) } }; } // 7. GetFriendList if (ep.includes('GetFriendList')) { const ids = Array.from({length: 8}, (_,i) => "76561198" + Math.floor(Math.random()*100000000)); return { friendslist: { friends: ids.map(id => ({ steamid: id, relationship: "friend", friend_since: 1500000000 })) } }; } // 8. GetNewsForApp if (ep.includes('GetNewsForApp')) { return { appnews: { newsitems: [ { title: "Patch Notes 1.5", url: "#", feedlabel: "Community Announcements", date: Date.now()/1000 - 86400, contents: "We've fixed several bugs and added new content." }, { title: "Weekend Event Update", url: "#", feedlabel: "Events", date: Date.now()/1000 - 172800, contents: "Join us this weekend for double XP and special drops!" } ] } }; } throw new Error("Mock API Not Implemented: " + ep); } async function mockStoreFetch(ep) { await delay(200 + Math.random() * 300); // 1. Feature Categories (Deals, Top Sellers) if (ep.includes('featuredcategories')) { const genItem = (g) => ({ id: g.appid, name: g.name, large_capsule_image: `https://cdn.akamai.steamstatic.com/steam/apps/${g.appid}/header.jpg`, final_price: g.price, original_price: g.price ? g.price + 1000 : 0, discount_percent: g.price ? 20 : 0 }); return { new_releases: { items: DB_GAMES.slice(4, 9).map(genItem) }, top_sellers: { items: DB_GAMES.slice(0, 10).map(genItem) }, specials: { items: DB_GAMES.slice(5, 12).map(genItem) } }; } // 2. AppDetails if (ep.includes('appdetails')) { const url = new URLSearchParams(ep.split('?')[1]); const appid = parseInt(url.get('appids')); const game = DB_GAMES.find(g => g.appid === appid) || DB_GAMES[0]; return { [appid]: { success: true, data: { name: game.name, is_free: game.is_free, header_image: `https://cdn.akamai.steamstatic.com/steam/apps/${game.appid}/header.jpg`, short_description: `Experience the thrilling world of ${game.name}. Embark on an epic journey filled with action and adventure.`, developers: [game.dev], price_overview: { final: game.price }, platforms: { windows: true, mac: false, linux: false }, metacritic: { score: game.mcc }, genres: game.tags.slice(0,2).map(t => ({description: t})), release_date: { date: "12 Oct, 2020" } } } }; } // 3. StoreSearch if (ep.includes('storesearch')) { const url = new URLSearchParams(ep.split('?')[1]); const term = url.get('term').toLowerCase(); const matched = DB_GAMES.filter(g => g.name.toLowerCase().includes(term)); return { items: matched.map(g => ({ id: g.appid, name: g.name, price: { final: g.price }, tiny_image: `https://cdn.akamai.steamstatic.com/steam/apps/${g.appid}/header.jpg` })) }; } throw new Error("Mock Store Not Implemented: " + ep); } // ── STATE ───────────────────────────────────────────────────────────────────── let curID = null; let myGames = []; let libFilter = 'all'; let stype = 'user'; // ── HELPERS ─────────────────────────────────────────────────────────────────── const id = i => document.getElementById(i); function gameImg(appid) { return `https://cdn.akamai.steamstatic.com/steam/apps/${appid}/header.jpg`; } function fmtH(m) { if (!m) return '0h'; const h = Math.round(m/60); return h >= 1000 ? (h/1000).toFixed(1)+'k h' : h+'h'; } function fmtD(ts) { if (!ts) return '—'; return new Date(ts*1000).toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}); } function fmtNum(n){ return n >= 1000000 ? (n/1000000).toFixed(1)+'M' : n >= 1000 ? (n/1000).toFixed(0)+'K' : String(n); } function esc(s) { return (s||'').replace(/'/g,'\\x27').replace(/"/g,'"').replace(//g,'>'); } function toast(msg) { const t = id('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 2600); } function loading(show, msg='LOADING...') { id('lmsg').textContent = msg; id('lov').classList.toggle('show', show); } // Map actual app to mock API engine async function apiFetch(ep) { return mockApiFetch(ep); } async function storeFetch(ep) { return mockStoreFetch(ep); } // ── SEARCH TYPES ───────────────────────────────────────────────────────────── function setStype(t, btn) { playSfx('click'); stype = t; document.querySelectorAll('.stype').forEach(b => b.classList.remove('on')); btn.classList.add('on'); id('search-input').placeholder = t === 'user' ? 'Search users...' : 'Search games...'; } function doSearch() { playSfx('click'); const v = id('search-input').value.trim(); if (!v) { toast('Enter a search query'); playSfx('error'); return; } id('search-input').blur(); // Close keyboard if (stype === 'user') loadProfile(v); else searchGames(v); } function heroSearch() { playSfx('click'); const v = id('hero-input').value.trim(); if (!v) { toast('Enter a Steam ID or game name'); playSfx('error'); return; } id('hero-input').blur(); // Close keyboard if (/^\d{17}$/.test(v) || /steamcommunity\.com/.test(v) || (!/\s/.test(v) && v.length < 30)) { loadProfile(v); } else { searchGames(v); } } id('search-input').addEventListener('keydown', e => e.key==='Enter' && doSearch()); id('hero-input').addEventListener('keydown', e => e.key==='Enter' && heroSearch()); // ── SHOW / HIDE VIEWS ───────────────────────────────────────────────────────── function showView(v) { id('home-view').style.display = v==='home' ? 'block' : 'none'; id('user-view').style.display = v==='user' ? 'block' : 'none'; id('gsearch-view').style.display= v==='gsearch'? 'block' : 'none'; id('app-container').scrollTo({top:0, behavior:'smooth'}); } // ── TRENDING HOME ───────────────────────────────────────────────────────────── async function loadTrend(type, btn) { playSfx('click'); document.querySelectorAll('.ttab').forEach(b => b.classList.remove('on')); btn.classList.add('on'); const el = id('trend-content'); el.innerHTML = '
LOADING...
'; try { if (type === 'top') { const data = await apiFetch('ISteamChartsService/GetMostPlayedGames/v1/'); const ranks = data?.response?.ranks || []; if (!ranks.length) throw new Error('No data'); const maxP = ranks[0]?.concurrent_in_game || 1; el.innerHTML = `
LIVE CONCURRENT PLAYERS · ${new Date().toLocaleTimeString()}
${ranks.slice(0,10).map((g,i) => `
${i+1}
${g.name||'App '+g.appid}
▶ ${fmtNum(g.concurrent_in_game)} playing now
`).join('')}
`; } else { const data = await storeFetch('api/featuredcategories/?cc=us&l=en'); const catMap = { new:'new_releases', top_sellers:'top_sellers', deals:'specials' }; const cat = catMap[type] || 'top_sellers'; const items = data?.[cat]?.items || []; if (type === 'deals') { el.innerHTML = `
CURRENT DEALS
${items.slice(0,10).map(g => { const final = g.final_price ? `$${(g.final_price/100).toFixed(2)}` : 'Free'; return `
${g.name}
${g.discount_percent ? `-${g.discount_percent}%` : ''} ${final}
`; }).join('')}
${items.slice(0,10).map((g,i) => { const final = g.final_price ? `$${(g.final_price/100).toFixed(2)}` : 'Free'; return `
${i+1}
${g.name}
${final}${g.discount_percent?` · -${g.discount_percent}% off`:''}
`; }).join('')}
`; } else { const featured = items[0]; el.innerHTML = ` ${featured ? `
${featured.name}
${featured.final_price ? `$${(featured.final_price/100).toFixed(2)}` : 'Free'} ${featured.discount_percent ? `-${featured.discount_percent}%` : ''}
` : ''}
${items.slice(0,10).map((g,i) => { const price = g.final_price ? `$${(g.final_price/100).toFixed(2)}` : 'Free'; return `
${i+1}
${g.name}
${price}${g.discount_percent?` · -${g.discount_percent}%`:''}
`; }).join('')}
`; } } } catch(e) { el.innerHTML = `
Could not load data — ${e.message}
`; } } // ── RESOLVE STEAM ID ────────────────────────────────────────────────────────── async function resolveID(input) { input = input.trim(); const m1 = input.match(/steamcommunity\.com\/profiles\/(\d{17})/); if (m1) return m1[1]; const m2 = input.match(/steamcommunity\.com\/id\/([^\/\s]+)/); if (m2) input = m2[1]; if (/^\d{17}$/.test(input)) return input; const d = await apiFetch(`ISteamUser/ResolveVanityURL/v1/?vanityurl=${encodeURIComponent(input)}`); if (d.response?.success === 1) return d.response.steamid; throw new Error('Cannot resolve "'+input+'"'); } // ── LOAD PROFILE ────────────────────────────────────────────────────────────── async function loadProfile(input) { loading(true, 'LOADING PROFILE...'); try { const steamid = await resolveID(input); curID = steamid; myGames = []; showView('user'); document.querySelectorAll('.tab').forEach(b => b.classList.remove('on')); document.querySelectorAll('.panel').forEach(p => p.classList.remove('on')); document.querySelector('.tab').classList.add('on'); id('tab-overview').classList.add('on'); await renderProfile(steamid); playSfx('success'); loading(false); } catch(e) { loading(false); playSfx('error'); toast('❌ ' + (e.message || 'Failed to load profile')); } } async function renderProfile(steamid) { const [sumData, gamesData] = await Promise.all([ apiFetch(`ISteamUser/GetPlayerSummaries/v2/?steamids=${steamid}`), apiFetch(`IPlayerService/GetOwnedGames/v1/?steamid=${steamid}&include_appinfo=1&include_played_free_games=1`) ]); const p = sumData.response?.players?.[0]; if (!p) throw new Error('Profile not found'); myGames = (gamesData.response?.games || []).sort((a,b) => (b.playtime_forever||0)-(a.playtime_forever||0)); renderPCard(p, myGames); renderOverview(p, myGames); renderLibrary(myGames); renderStats(myGames, p); renderAchSel(myGames); renderReco(myGames); loadFriends(steamid); } // ── PROFILE CARD ────────────────────────────────────────────────────────────── function renderPCard(p, games) { const sm = {0:'Offline',1:'Online',2:'Busy',3:'Away',4:'Snooze',5:'Looking to trade',6:'Looking to play'}; const cls = p.personastate===1?'online':p.gameextrainfo?'ingame':'offline'; const st = p.gameextrainfo?('In-Game: '+p.gameextrainfo):(sm[p.personastate]||'Offline'); const hrs = Math.round(games.reduce((s,g)=>s+(g.playtime_forever||0),0)/60); const pl = games.filter(g=>g.playtime_forever>0).length; id('pcard').innerHTML = `
${p.personaname}
${st}
${games.length.toLocaleString()}
GAMES
${hrs>=1000?(hrs/1000).toFixed(1)+'k':hrs}
HOURS
${pl}
PLAYED
${p.loccountrycode?`
🌍 ${p.loccountrycode}
`:''} ${p.timecreated?`
📅 Since ${fmtD(p.timecreated)}
`:''}
🆔 ${p.steamid}
${p.profileurl?``:''}
`; id('cmp-users').innerHTML = `
${p.personaname}
${games.length} games · ${hrs>=1000?(hrs/1000).toFixed(1)+'k':hrs}h
VS
Enter friend below
`; } // ── OVERVIEW ────────────────────────────────────────────────────────────────── async function renderOverview(p, games) { try { const r = await apiFetch(`IPlayerService/GetRecentlyPlayedGames/v1/?steamid=${curID}&count=8`); const rec = r.response?.games || []; id('recent-act').innerHTML = !rec.length ? '
😴
No recent activity
' : rec.map(g=>`
${g.name}
${fmtH(g.playtime_2weeks)} last 2 wks · ${fmtH(g.playtime_forever)} total
`).join(''); } catch { id('recent-act').innerHTML = '
Could not load recent activity
'; } id('top-games').innerHTML = games.slice(0,5).map(g=>gcHtml(g)).join(''); } // ── LIBRARY ─────────────────────────────────────────────────────────────────── function gcHtml(g) { return `
${g.name}
${fmtH(g.playtime_forever)} ${g.playtime_2weeks?`▶ ${fmtH(g.playtime_2weeks)} recent`:''}
`; } function fLib(t, btn) { playSfx('click'); libFilter = t; document.querySelectorAll('.fb').forEach(b=>b.classList.remove('on')); btn.classList.add('on'); renderLibrary(myGames); } function renderLibrary(games) { let f = [...games]; if (libFilter==='played') f = f.filter(g=>g.playtime_forever>0); if (libFilter==='unplayed') f = f.filter(g=>!g.playtime_forever); if (libFilter==='recent') f = f.filter(g=>g.playtime_2weeks>0).sort((a,b)=>b.playtime_2weeks-a.playtime_2weeks); id('lib-cnt').textContent = `${f.length} of ${games.length} games`; id('lib-list').innerHTML = !f.length ? '
📭
No games found
' : f.map(g=>gcHtml(g)).join(''); } // ── STATS ───────────────────────────────────────────────────────────────────── function renderStats(games, p) { const totH = Math.round(games.reduce((s,g)=>s+(g.playtime_forever||0),0)/60); const pl = games.filter(g=>g.playtime_forever>0); const unpl = games.filter(g=>!g.playtime_forever); const avg = pl.length ? Math.round(totH/pl.length) : 0; const rec = games.filter(g=>g.playtime_2weeks>0); const recH = Math.round(rec.reduce((s,g)=>s+(g.playtime_2weeks||0),0)/60); const cr = Math.round(pl.length/Math.max(games.length,1)*100); const top10= games.slice(0,10); id('stats-con').innerHTML = `
${totH>=1000?(totH/1000).toFixed(1)+'k':totH}
TOTAL HOURS
${games.length}
GAMES OWNED
${pl.length}
GAMES PLAYED
${unpl.length}
NEVER PLAYED
${avg}h
AVG PER GAME
${cr}%
PLAY RATE
${recH}h
LAST 2 WEEKS · ${rec.length} ACTIVE GAMES
TOP 10 BY PLAYTIME
${top10.map(g=>`
${g.name}
${fmtH(g.playtime_forever)}
`).join('')} `; } // ── ACHIEVEMENTS ────────────────────────────────────────────────────────────── function renderAchSel(games) { const top = games.filter(g=>g.playtime_forever>0).slice(0,20); id('ach-sel').innerHTML = `
Pick a game:
`; id('ach-list').innerHTML = '
🏆
Select a game above
'; } async function loadAchs(appid) { if (!appid) return; id('ach-list').innerHTML = '
LOADING...
'; try { const [schData, plData] = await Promise.all([ apiFetch(`ISteamUserStats/GetSchemaForGame/v2/?appid=${appid}`), apiFetch(`ISteamUserStats/GetPlayerAchievements/v1/?appid=${appid}&steamid=${curID}`) ]); const schema = schData.game?.availableGameStats?.achievements || []; const plAch = plData.playerstats?.achievements || []; const map = {}; plAch.forEach(a=>map[a.apiname]=a); if (!schema.length) { id('ach-list').innerHTML='
🚫
No achievements
'; return; } const done = schema.filter(a=>map[a.name]?.achieved===1); const locked = schema.filter(a=>map[a.name]?.achieved!==1); const pct = Math.round(done.length/schema.length*100); id('ach-list').innerHTML = `
${done.length} / ${schema.length} Unlocked
${pct}%
UNLOCKED ${done.length}
${done.map(a=>`
${a.displayName||a.name}
${a.description||''}
Unlocked ${fmtD(map[a.name]?.unlocktime)}
`).join('')}
${locked.length?`
LOCKED ${locked.length}
${locked.slice(0,25).map(a=>`
${a.displayName||a.name}
${a.description||'Hidden achievement'}
🔒
`).join('')}
`:''} `; } catch(e) { id('ach-list').innerHTML = '
Could not load stats
'; } } // ── FRIENDS ─────────────────────────────────────────────────────────────────── async function loadFriends(steamid) { const el = id('friend-list'); el.innerHTML = '
LOADING FRIENDS...
'; try { const fData = await apiFetch(`ISteamUser/GetFriendList/v1/?steamid=${steamid}&relationship=friend`); const frs = fData.friendslist?.friends || []; if (!frs.length) { el.innerHTML='
🔒
Friends list is private
'; return; } const ids = frs.slice(0,10).map(f=>f.steamid).join(','); const sData = await apiFetch(`ISteamUser/GetPlayerSummaries/v2/?steamids=${ids}`); const players = (sData.response?.players||[]).sort((a,b)=>b.personastate-a.personastate); const sm = {0:'Offline',1:'Online',2:'Busy',3:'Away'}; el.innerHTML = players.map(p=>{ const cls = p.personastate===1?'online':p.gameextrainfo?'ingame':'offline'; const st = p.gameextrainfo||sm[p.personastate]||'Offline'; return `
${p.personaname}
${st}
`; }).join(''); } catch { el.innerHTML = '
Friends list unavailable
'; } } // ── COMPARE ─────────────────────────────────────────────────────────────────── async function doCompare() { playSfx('click'); const inp = id('cmp-input').value.trim(); if (!inp) { toast('Enter a Steam ID or vanity URL'); playSfx('error'); return; } id('cmp-input').blur(); const el = id('cmp-results'); el.innerHTML = '
COMPARING...
'; try { const id2 = await resolveID(inp); const [p2Data, g2Data] = await Promise.all([ apiFetch(`ISteamUser/GetPlayerSummaries/v2/?steamids=${id2}`), apiFetch(`IPlayerService/GetOwnedGames/v1/?steamid=${id2}&include_appinfo=1&include_played_free_games=1`) ]); const p2 = p2Data.response?.players?.[0]; const g2 = g2Data.response?.games || []; const g2Ids = new Set(g2.map(g=>g.appid)); const common = myGames.filter(g=>g2Ids.has(g.appid)); const hrs2 = Math.round(g2.reduce((s,g)=>s+(g.playtime_forever||0),0)/60); id('cu2').innerHTML = `
${p2?.personaname||'Unknown'}
${g2.length} games · ${hrs2>=1000?(hrs2/1000).toFixed(1)+'k':hrs2}h
`; el.innerHTML = `
${common.length}
COMMON
${myGames.length-common.length}
ONLY MINE
${g2.length-common.length}
ONLY THEIRS
GAMES YOU BOTH OWN ${common.length}
${common.slice(0,40).map(g=>{ const g2e = g2.find(x=>x.appid===g.appid); return `
${g.name}
Me: ${fmtH(g.playtime_forever)} Them: ${fmtH(g2e?.playtime_forever)}
`; }).join('')} ${common.length>40?`
+${common.length-40} more
`:''}
`; playSfx('success'); } catch(e) { el.innerHTML = `
${e.message||'Compare failed'}
`; playSfx('error'); } } // ── RECOMMENDATIONS ─────────────────────────────────────────────────────────── async function renderReco(games) { const el = id('reco-con'); el.innerHTML = '
GENERATING...
'; const top = games.filter(g=>g.playtime_forever>0).slice(0,3); if (!top.length) { el.innerHTML='
🎮
Play some games first!
'; return; } try { const feat = await storeFetch('api/featuredcategories/?cc=us&l=en'); const ownIds = new Set(games.map(g=>g.appid)); let cands = []; ['top_sellers','specials','new_releases'].forEach(cat=>{ (feat?.[cat]?.items||[]).forEach(item=>{ if(item.id && !ownIds.has(item.id)) cands.push(item); }); }); const seen = new Set(); cands = cands.filter(c=>{ if(seen.has(c.id))return false; seen.add(c.id); return true; }).slice(0,10); const topNames = top.map(g=>g.name).slice(0,2).join(', '); el.innerHTML = !cands.length ? '
🔍
No recs found
' : cands.map(c=>{ const price = c.final_price ? `$${(c.final_price/100).toFixed(2)}` : 'Free'; return `
${c.name||'Unknown'}
${price} ${c.discount_percent?`-${c.discount_percent}%`:''}
📊 Because you play: ${topNames}
`; }).join(''); } catch { el.innerHTML='
Could not load recommendations
'; } } // ── GAME SEARCH ─────────────────────────────────────────────────────────────── async function searchGames(q) { loading(true, 'SEARCHING...'); showView('gsearch'); const el = id('gsearch-results'); el.innerHTML = '
'; try { const d = await storeFetch(`api/storesearch/?term=${encodeURIComponent(q)}`); const items = d?.items || []; el.innerHTML = !items.length ? `
🔍
No results for "${q}"
` : `
${items.length} RESULTS "${q}"
` + items.map(g=>{ const price = g.price?.final ? `$${(g.price.final/100).toFixed(2)}` : 'Free'; return `
${g.name}
AppID: ${g.id}
${price}
`; }).join(''); loading(false); playSfx('success'); } catch(e) { el.innerHTML=`
Search failed: ${e.message}
`; loading(false); playSfx('error'); } } // ── GAME DETAIL ─────────────────────────────────────────────────────────────── async function openGD(appid, name) { playSfx('click'); const overlay = id('gd'); id('gd-title').textContent = name; id('gd-hero').src = gameImg(appid); id('gd-tags').innerHTML = ''; id('gd-desc').innerHTML = '
'; id('gd-stats').innerHTML = ''; id('gd-extra').innerHTML = ''; overlay.classList.add('on'); overlay.scrollTop = 0; try { const [detData, newsData] = await Promise.all([ storeFetch(`api/appdetails?appids=${appid}`), apiFetch(`ISteamNews/GetNewsForApp/v2/?appid=${appid}`) ]); const app = detData?.[appid]?.data; const news = newsData?.appnews?.newsitems || []; if (!app) throw new Error('No store data'); id('gd-title').textContent = app.name || name; id('gd-hero').src = app.header_image || gameImg(appid); const tags = [...(app.genres||[]).map(g=>g.description), ...(app.categories||[]).slice(0,3).map(c=>c.description)]; id('gd-tags').innerHTML = tags.map(t=>`${t}`).join(''); id('gd-desc').textContent = (app.short_description||'').replace(/<[^>]+>/g,'')||'No description available.'; const price = app.is_free ? 'Free' : (app.price_overview ? `$${(app.price_overview.final/100).toFixed(2)}` : 'N/A'); const dev = (app.developers||['Unknown'])[0]; const mc = app.metacritic?.score; const plat = Object.keys(app.platforms||{}).filter(k=>app.platforms[k]).join(', '); const owned = curID && myGames.find(g=>g.appid===appid); id('gd-stats').innerHTML = `
${price}
PRICE
${app.release_date?.date||'—'}
RELEASED
${dev}
DEVELOPER
${mc?`
${mc}
METACRITIC
`:''}
${plat||'—'}
PLATFORMS
${appid}
APP ID
${owned?`
✅ You Own This
${fmtH(owned.playtime_forever)} PLAYED
`:''} `; let extra = ''; if (news.length) { extra += `
LATEST NEWS
` + news.map(n=>`
${n.title}
${n.feedlabel}${fmtD(n.date)}
${(n.contents||'').replace(/<[^>]+>/g,'').slice(0,120)}...
`).join(''); } id('gd-extra').innerHTML = extra; } catch(e) { id('gd-desc').innerHTML = `
Could not load game details
`; } } function closeGD() { playSfx('click'); id('gd').classList.remove('on'); } // ── TABS ────────────────────────────────────────────────────────────────────── function swTab(name, btn) { playSfx('click'); document.querySelectorAll('.tab').forEach(b=>b.classList.remove('on')); document.querySelectorAll('.panel').forEach(p=>p.classList.remove('on')); btn.classList.add('on'); id('tab-'+name).classList.add('on'); id('app-container').scrollTo({top:0, behavior:'smooth'}); } // ── INIT ────────────────────────────────────────────────────────────────────── function initApp() { applyAllEditableValues(); initBGM(); showView('home'); loadTrend('top', document.querySelector('.ttab')); } document.addEventListener('DOMContentLoaded', initApp);