StableSMP • Market
Player Stock Market
Visual market board with live prices and cached mini charts.
loading...
(function(){ const API="https://api.codetabs.com/v1/proxy/?quest=http://15.235.31.67:24990/api/market?token=4379kerrydrstablesmp"; const CACHE="stable_stock_visual_chart_v1"; const HISTORY="stable_stock_visual_chart_history_v1"; let players=[],query="",openId=null,selectedId=null; function save(raw){ try{ localStorage.setItem(CACHE,raw); localStorage.setItem(CACHE+"_t",Date.now()); }catch(e){} } function load(){ try{return localStorage.getItem(CACHE)}catch(e){return null} } function loadTime(){ try{return Number(localStorage.getItem(CACHE+"_t")||0)}catch(e){return 0} } function loadHistory(){ try{return JSON.parse(localStorage.getItem(HISTORY)||"{}")}catch(e){return {}} } function saveHistory(h){ try{localStorage.setItem(HISTORY,JSON.stringify(h))}catch(e){} } function clean(s){ return String(s||"").replace(/&[0-9a-fk-or]/gi,"").replace(/§[0-9a-fk-or]/gi,"").trim(); } function money(n){ n=Number(n||0); if(n>=1e12)return"$"+(n/1e12).toFixed(1).replace(/\.0$/,"")+"t"; if(n>=1e9)return"$"+(n/1e9).toFixed(1).replace(/\.0$/,"")+"b"; if(n>=1e6)return"$"+(n/1e6).toFixed(1).replace(/\.0$/,"")+"m"; if(n>=1e3)return"$"+(n/1e3).toFixed(1).replace(/\.0$/,"")+"k"; return"$"+(Math.round(n*100)/100).toFixed(2); } function compactMoney(n){ n=Number(n||0); if(n>=1e12)return"$"+(n/1e12).toFixed(1).replace(/\.0$/,"")+"t"; if(n>=1e9)return"$"+(n/1e9).toFixed(1).replace(/\.0$/,"")+"b"; if(n>=1e6)return"$"+(n/1e6).toFixed(1).replace(/\.0$/,"")+"m"; if(n>=1e3)return"$"+(n/1e3).toFixed(1).replace(/\.0$/,"")+"k"; return"$"+Math.round(n).toLocaleString(); } function num(v,fallback){ if(v===""||v===null||v===undefined)return fallback; var n=Number(v); if(!isFinite(n))return fallback; return Math.floor(n)===n?String(n):String(Math.round(n*100)/100); } function pct(v){ v=Number(v||0); var sign=v>0?"+":""; return sign+(Math.round(v*100)/100).toFixed(2)+"%"; } function colorClass(v){ v=Number(v||0); if(v>0)return"good"; if(v<0)return"bad"; return""; } function formatLastSeen(ms){ ms=Number(ms||0); if(!ms)return"Unknown"; var diff=Date.now()-ms; if(diff<120000)return"just now"; var mins=Math.floor(diff/60000); if(mins<60)return mins+"m ago"; var hrs=Math.floor(mins/60); if(hrs<24)return hrs+"h ago"; var days=Math.floor(hrs/24); return days+"d ago"; } function makeHead(name,big){ const img=document.createElement("img"); img.className=big?"feature-avatar":"player-head"; img.src="https://mc-heads.net/avatar/"+encodeURIComponent(name||"Steve")+"/64"; img.alt=name||"Player"; img.onerror=function(){ const fallback=document.createElement("div"); fallback.className=big?"feature-fallback":"player-head-fallback"; fallback.textContent=String(name||"?").charAt(0); if(img.parentNode) img.parentNode.replaceChild(fallback,img); }; return img; } function sort(list){ return list.slice().sort(function(a,b){ return (Number(b.price)||0)-(Number(a.price)||0); }); } function updateHistory(){ var h=loadHistory(); players.forEach(function(p){ if(!h[p.uuid])h[p.uuid]=[]; h[p.uuid].push(Number(p.price||0)); if(h[p.uuid].length>20)h[p.uuid]=h[p.uuid].slice(h[p.uuid].length-20); }); saveHistory(h); } function sparkline(uuid,w,h){ var hist=loadHistory()[uuid]||[]; if(!hist.length){ return ''; } var min=Math.min.apply(null,hist),max=Math.max.apply(null,hist),range=max-min; if(range===0)range=1; var pts=hist.map(function(v,i){ var x=hist.length===1?0:(i*(w/(hist.length-1))); var y=h-((v-min)/range*h); return x.toFixed(2)+","+y.toFixed(2); }).join(" "); var color=(hist[hist.length-1]-hist[0])>=0?"#34ee80":"#ff7b7b"; return ''; } function buildStats(p){ const g=document.createElement("div"); g.className="stat-grid"; function add(k,v){ const c=document.createElement("div"); c.className="stat-card"; c.innerHTML='
'+k+'
'+v+'
'; g.appendChild(c); } add("Price",money(p.price)); add("Previous",money(p.previousPrice)); add("Change",''+pct(p.changePercent)+''); add("Balance",compactMoney(p.balance)); add("Kills",num(p.kills,"0")); add("Deaths",num(p.deaths,"0")); add("K/D",num(p.kd,"0")); add("Playtime",num(Math.floor((Number(p.playMinutes)||0)/60),"0")+"h"); add("Last Seen",formatLastSeen(p.lastSeen)); add("Bounty",money(p.activeBounty||0)); return g; } function row(p,i){ const r=document.createElement("div"); r.className="player-row"+(openId===p.uuid?" open":""); const top=document.createElement("div"); top.className="player-top"; top.onclick=function(){ openId=openId===p.uuid?null:p.uuid; selectedId=p.uuid; draw(); }; const head=makeHead(p.name,false); const main=document.createElement("div"); main.className="player-main"; const nameRow=document.createElement("div"); nameRow.className="player-name-row"; const rank=document.createElement("div"); rank.className="rank-badge"; rank.textContent="#"+i; const name=document.createElement("div"); name.className="player-name"; name.textContent=(p.name||"Unknown"); nameRow.appendChild(rank); nameRow.appendChild(name); const bal=document.createElement("div"); bal.className="player-balance"; bal.textContent=money(p.price); const mini=document.createElement("div"); mini.className="player-mini"; mini.innerHTML=''+pct(p.changePercent)+' • '+compactMoney(p.balance)+' balance'; main.appendChild(nameRow); main.appendChild(bal); main.appendChild(mini); const arrow=document.createElement("div"); arrow.className="player-arrow"; arrow.textContent="›"; top.appendChild(head); top.appendChild(main); top.appendChild(arrow); r.appendChild(top); const details=document.createElement("div"); details.className="player-details"; const title=document.createElement("div"); title.className="player-details-title"; title.textContent="Stock Stats"; details.appendChild(title); details.appendChild(buildStats(p)); const graph=document.createElement("div"); graph.className="graph-box"; graph.innerHTML=sparkline(p.uuid,360,80); details.appendChild(graph); r.appendChild(details); return r; } function drawFeature(){ const el=document.getElementById("feature"); if(!players.length){ el.innerHTML='
Featured Stock
no market data
'; return; } var target=null; if(selectedId) target=players.filter(function(p){return p.uuid===selectedId;})[0]||null; if(!target) target=sort(players)[0]; el.innerHTML=""; const head=document.createElement("div"); head.className="feature-head"; head.innerHTML='
Featured Stock
'; el.appendChild(head); const grid=document.createElement("div"); grid.className="feature-grid"; grid.appendChild(makeHead(target.name,true)); const right=document.createElement("div"); right.innerHTML= '
'+target.name+'
'+ '
'+money(target.price)+'
'; const graph=document.createElement("div"); graph.className="graph-box"; graph.innerHTML=sparkline(target.uuid,520,100); right.appendChild(graph); grid.appendChild(right); el.appendChild(grid); } function draw(){ const list=document.getElementById("list"); list.innerHTML=""; let filtered=players.filter(function(p){ return (p.name||"").toLowerCase().includes(query); }); filtered=sort(filtered); if(!filtered.length){ list.innerHTML='
no stocks found
'; drawFeature(); updateStatus(); return; } let i=1; filtered.forEach(function(p){ list.appendChild(row(p,i++)); }); drawFeature(); updateStatus(); } function updateStatus(){ const t=loadTime(); const age=t?Math.floor((Date.now()-t)/1000):0; const totalCount=players.length; const avg=players.length?players.reduce(function(sum,p){return sum+(Number(p.price)||0)},0)/players.length:0; document.getElementById("status").innerHTML= 'Tracked: '+totalCount+''+ 'Avg Price: '+money(avg)+''+ ''+(t?'Updated '+age+'s ago':'No data')+''; } function use(raw){ try{ const d=JSON.parse(raw); players=Array.isArray(d)?d:(d.stocks||[]); players=players.map(function(p){ var kills=Number(p.kills||0); var deaths=Number(p.deaths||0); return { uuid:p.uuid||p.name||"", name:p.name||"Unknown", price:Number(p.price||0), previousPrice:Number(p.previousPrice||0), changePercent:Number(p.changePercent||0), balance:Number(p.balance||0), kills:kills, deaths:deaths, kd:deaths>0?(kills/deaths):kills, playMinutes:Number(p.playMinutes||0), lastSeen:Number(p.lastSeen||0), activeBounty:Number(p.activeBounty||0) }; }); updateHistory(); draw(); }catch(e){ console.log("bad json",e); document.getElementById("status").innerHTML='bad json'; } } function fetchData(){ fetch(API,{cache:"no-store"}) .then(function(r){ if(!r.ok) throw new Error("HTTP "+r.status); return r.text(); }) .then(function(t){ save(t); use(t); }) .catch(function(){ const c=load(); if(c) use(c); else{ players=[]; draw(); document.getElementById("status").innerHTML='Failed to load market'; } }); } document.getElementById("search").oninput=function(e){ query=e.target.value.toLowerCase(); draw(); }; const boot=load(); if(boot)use(boot); else draw(); fetchData(); setInterval(fetchData,30000); })();