summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-08 03:34:38 +0100
committersalmonik <salmonik@tuta.io>2026-11-08 03:34:38 +0100
commit408fac5c545c4294c81df5fff36b2fed19d7fe55 (patch)
treeef5a05d8099a1531f88cd3e5008225ea47910fc4 /web
parentweb: accept the serialized reels array and stop game loops on navigation (diff)
downloadzagan-bet-408fac5c545c4294c81df5fff36b2fed19d7fe55.tar.gz
zagan-bet-408fac5c545c4294c81df5fff36b2fed19d7fe55.zip
web: restore the balance on a refresh into history
The gateway hello now carries the core's last known balance alongside the user, so a refresh on a non-game view does not blank the header before the next INFO reply.
Diffstat (limited to 'web')
-rw-r--r--web/play/app.js2
-rw-r--r--web/play/src/main.ts1
-rw-r--r--web/play/src/types.ts2
3 files changed, 4 insertions, 1 deletions
diff --git a/web/play/app.js b/web/play/app.js
index 4950c5e..25b10a6 100644
--- a/web/play/app.js
+++ b/web/play/app.js
@@ -1 +1 @@
"use strict";(()=>{function k(e){let n=document.getElementById(e);if(!n)throw new Error(`missing #${e}`);return n}function o(e,n,t){let s=document.createElement(e);return n&&(s.className=n),t!==void 0&&(s.textContent=t),s}function H(e){e.replaceChildren()}function Ke(e){let n=e<0,t=String(Math.abs(Math.trunc(e))),s="";for(let a=0;a<t.length;a+=1)a>0&&(t.length-a)%3===0&&(s+=" "),s+=t[a];return n?`-${s}`:s}function L(e){return e==null||Number.isNaN(e)?"— Kr":`${Ke(e)} Kr`}function he(e){return`${e<0?"-":"+"}${L(Math.abs(e))}`}function ee(e,n="info"){let t=document.getElementById("toasts");if(!t||!e)return;let s=o("div",`toast ${n}`,e);for(t.append(s),window.setTimeout(()=>{s.style.opacity="0",s.style.transition="opacity 220ms ease",window.setTimeout(()=>s.remove(),240)},4200);t.children.length>4;)t.firstElementChild?.remove()}function be(e){let n=e.toLowerCase();return e.startsWith("ERROR")||n.includes("failed")||n.includes("lost")||n.includes("invalid")||n.includes("forfeit")?"bad":e.startsWith("Logged in")||e.startsWith("Account created")||n.includes("won")||n.includes("cashed")?"good":"info"}async function ge(e){let n=new TextEncoder().encode(e),t=await crypto.subtle.digest("SHA-256",n);return[...new Uint8Array(t)].map(s=>s.toString(16).padStart(2,"0")).join("")}function z(e,n=10){return e.length<=n*2?e:`${e.slice(0,n)}…${e.slice(-n)}`}function ye(e){let n=Date.parse(e.includes("T")?e:e.replace(" ","T")+"Z");return Number.isNaN(n)?e:new Date(n).toLocaleString()}function ke(e){if(e==="?"||!e)return{rank:"?",suit:"",red:!1};let n=e.slice(-1);return{rank:e.slice(0,-1),suit:n==="C"?"♣":n==="D"?"♦":n==="H"?"♥":n==="S"?"♠":n,red:n==="D"||n==="H"}}var j=class{constructor(n,t){this.url=n;this.handlers=t;this.socket=null;this.stopped=!1;this.replaced=!1;this.attempts=0}connect(){if(this.stopped)return;this.clearTimer();let n;try{n=new WebSocket(this.url)}catch{this.scheduleReconnect()||this.handlers.onClosed(!1);return}this.socket=n,n.addEventListener("open",()=>{this.attempts=0,this.handlers.onOpen()}),n.addEventListener("message",t=>{let s;try{s=JSON.parse(String(t.data))}catch{return}switch(s.type){case"hello":this.handlers.onHello(s);break;case"view":this.handlers.onView(s.view);break;case"notice":this.handlers.onNotice(String(s.text??""));break;case"error":this.handlers.onError(String(s.text??""));break;case"replaced":this.replaced=!0,this.handlers.onReplaced();break;case"pong":this.handlers.onPong();break}}),n.addEventListener("close",()=>{if(this.socket===n&&(this.socket=null),this.replaced||this.stopped){this.handlers.onClosed(!1);return}let s=this.scheduleReconnect();this.handlers.onClosed(s)}),n.addEventListener("error",()=>{})}send(n){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({type:"command",command:n}))}ping(){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({type:"ping"}))}close(){this.stopped=!0,this.clearTimer(),this.socket?.close()}scheduleReconnect(){if(this.stopped||this.replaced||this.attempts>=6)return!1;this.attempts+=1;let n=Math.min(250*2**(this.attempts-1),4e3);return this.timer=window.setTimeout(()=>this.connect(),n),!0}clearTimer(){this.timer!==void 0&&(window.clearTimeout(this.timer),this.timer=void 0)}};var Le=0;function te(){return Date.now()-Le<400?!1:(Le=Date.now(),!0)}function Ue(e){if(e==="?"){let s=o("div","card back");return s.append(o("span","rank","?"),o("span","suit","")),s}let n=ke(e),t=o("div","card");return n.red&&t.classList.add("red"),t.append(o("span","rank",n.rank),o("span","suit",n.suit)),t}function xe(e,n,t){let s=o("div","hand");s.append(o("div","label",e));let a=o("div","cards");for(let d of n)a.append(Ue(d));let r=o("div","total");return r.append(document.createTextNode("Total "),o("strong",void 0,String(t))),s.append(a,r),s}function Ze(e,n){let t=String(n?.result??""),s=Number(n?.winnings??0);switch(t){case"blackjack":return`Blackjack — won ${L(s)}`;case"win":return`You win — ${L(s)}`;case"push":return`Push — ${L(s)} returned`;case"bust":return"Bust — the bet is lost";case"lose":return"Dealer wins — the bet is lost";case"forfeit":return"Hand forfeited — the bet is lost";case"playing":return e.status==="player"?"Your move.":"Dealing…";default:return"Ready to deal."}}function we(e,n,t,s){H(e),H(n);let a=t.hand,r=a?.phase==="player"||t.status==="player",d=a?.phase==="done"||t.status==="done",i=!a||d;if(e.append(o("h2",void 0,"Blackjack")),e.append(o("p","note","Six decks. Dealer stands on all 17. Blackjack pays 3:2.")),a)e.append(xe("Dealer",a.dealer??[],Number(a.dealer_total??0))),e.append(xe("You",a.player??[],Number(a.player_total??0)));else{let f=o("div","outcome");f.textContent="Press DEAL to start a hand.",e.append(f)}if(d||a&&!r){let f=o("div","outcome");f.textContent=Ze(t,a),(String(a?.result)==="win"||String(a?.result)==="blackjack"||String(a?.result)==="push")&&f.classList.add("win-text"),e.append(f)}t.error&&e.append(o("p","error-text",String(t.error))),n.append(o("div","label","Blackjack"));let m=Number(a?.bet??t.bet??0),p=t.balance??t.hand?.balance;if(n.append(o("p","note","Hit, stand or double. Forfeiting loses the reserved bet.")),i){let f=o("div","row"),w=o("button","btn small","−10"),y=o("strong","mono",L(m)),v=o("button","btn small","+10");w.addEventListener("click",()=>s({AdjustBet:-10})),v.addEventListener("click",()=>s({AdjustBet:10})),f.append(w,y,v),n.append(o("div","label","Bet"),f)}else{let f=o("div","stat");f.append(o("span","muted","Bet"),o("strong",void 0,L(m))),n.append(f)}if(p!==void 0){let f=o("div","stat");f.append(o("span","muted","Balance"),o("strong",void 0,L(Number(p)))),n.append(f)}let u=o("div","actions");if(r){let f=o("div","row wrap");for(let y of a?.actions??[]){let v=y==="hit"?"HIT":y==="stand"?"STAND":"DOUBLE",E=y==="stand"?o("button","btn primary",v):o("button","btn",v);E.addEventListener("click",()=>{te()&&s(y==="hit"?"Hit":y==="stand"?"Stand":"Double")}),f.append(E)}u.append(f);let w=o("button","btn danger wide","Forfeit hand");w.addEventListener("click",()=>{te()&&window.confirm("Forfeit this hand? The reserved bet is lost.")&&s("Forfeit")}),u.append(w)}else if(i){let f=o("button","btn primary wide big","DEAL");f.addEventListener("click",()=>{te()&&s("Deal")}),u.append(f)}let g=o("button","btn ghost wide",r?"Leave (forfeits)":"Leave table");g.addEventListener("click",()=>{r&&!window.confirm("Leaving forfeits this hand. Continue?")||s("Leave")}),u.append(g),n.append(u)}var ne=Math.LN2/5,R=null,I=null,Me=null,W=null,$,ve=0,Y="",Te=0;function Je(e,n,t,s){H(e),H(n),W=t,Me=s,e.append(o("h2",void 0,"Crash")),e.append(o("p","note","The multiplier rises from 1.00×. Cash out before it crashes."));let a=o("div","crash-wrap"),r=o("div","crash-number","1.00×"),d=o("div","crash-meta"),i=o("canvas","crash-canvas"),m=o("div","verify","Waiting for a round commitment.");a.append(r,d,i,m),e.append(a),n.append(o("div","label","Crash")),n.append(o("p","note","The server commits to a seed before the round. The seed is revealed when it ends."));let p=o("div","row"),u=o("button","btn small","−10"),g=o("strong","mono","—"),f=o("button","btn small","+10");u.addEventListener("click",()=>W?.({AdjustBet:-10})),f.addEventListener("click",()=>W?.({AdjustBet:10})),p.append(u,g,f),n.append(o("div","label","Bet"),p);let w=o("strong","mono","—"),y=o("strong","mono","—");for(let[M,N]of[["Potential",w],["Balance",y]]){let b=o("div","stat");b.append(o("span","muted",M),N),n.append(b)}let v=o("p","note","Ready."),E=o("p","note",""),S=o("p","error-text");n.append(v,E,S);let l=o("div","actions"),c=o("button","btn primary wide big","START"),h=o("button","btn ghost wide","Leave table");return c.addEventListener("click",()=>{Date.now()-Te<500||(Te=Date.now(),I?.status==="running"?W?.("CashOut"):W?.("Start"))}),h.addEventListener("click",()=>{I?.status==="running"&&!window.confirm("Forfeit this round? The bet is lost.")||W?.("Leave")}),l.append(c,h),n.append(l),{board:e,side:n,number:r,meta:d,verify:m,canvas:i,bet:g,potential:w,balance:y,status:v,lastCrash:E,error:S,primary:c,leave:h,minus:u,plus:f}}function Ce(){$!==void 0&&(cancelAnimationFrame($),$=void 0),I=null}function Se(e,n,t,s,a){(!R||R.board!==e||!R.canvas.isConnected)&&(R=Je(e,n,s,a),Y="");let r=R;W=s,Me=a,I=t,t.status!==Y&&((t.status==="starting"||t.status==="running")&&Y!=="running"&&(r.verify.className="verify",r.verify.textContent="Round committed; seed hidden until the round ends."),Y=String(t.status));let d=t.status==="running",i=t.status==="starting",m=d||i;r.bet.textContent=L(Number(t.bet??0)),r.potential.textContent=L(Number(t.potential??t.bet??0)),r.balance.textContent=t.balance===null||t.balance===void 0?"—":L(Number(t.balance)),r.error.textContent=t.error?String(t.error):"",r.minus.disabled=m,r.plus.disabled=m,r.primary.textContent=d?"CASH OUT":i?"STARTING…":"START",r.primary.disabled=i,r.primary.className="btn primary wide big",r.leave.textContent=d?"Forfeit round":"Leave table",d?(r.status.textContent="Rising — cash out before the crash.",r.status.className="note win-text"):i?(r.status.textContent="Starting the round…",r.status.className="note"):t.status==="cashed"?(r.status.textContent=`Cashed out at ${Number(t.multiplier??0).toFixed(2)}× for ${L(Number(t.payout??0))}.`,r.status.className="note win-text"):t.status==="crashed"?(r.status.textContent=`Crashed at ${Number(t.crash_point??t.multiplier??0).toFixed(2)}×. The bet is lost.`,r.status.className="note error-text"):t.status==="forfeit"?(r.status.textContent="Round forfeited.",r.status.className="note error-text"):(r.status.textContent="Ready. Start a round when you want.",r.status.className="note"),t.crash_point!==null&&t.crash_point!==void 0&&t.status==="crashed"&&(r.lastCrash.textContent=`Last crash: ${Number(t.crash_point).toFixed(2)}×`);let p=t.commit?String(t.commit):"";if(r.meta.textContent=p?`commit ${z(p,12)}`:"no commitment yet",t.seed&&p){let u=++ve,g=String(t.seed),f=Number(t.crash_point??0);et(g,p,f).then(([w,y])=>{if(u!==ve||!R)return;let v=w&&y;R.verify.className=`verify ${v?"ok":"bad"}`,R.verify.textContent=v?`Seed ${z(g,12)} · sha256 matches commit · crash point recomputed`:`VERIFICATION FAILED · seed ${z(g,12)} · commit ${z(p,12)}`})}else!m&&!p&&(r.verify.className="verify",r.verify.textContent="Waiting for a round commitment.");if($!==void 0&&(cancelAnimationFrame($),$=void 0),d){let u=()=>{if(!R||I?.status!=="running"){$=void 0;return}Ee(R,I,a),$=requestAnimationFrame(u)};$=requestAnimationFrame(u)}else Ee(r,t,a)}function qe(e,n){if(e.status==="running"){let t=Math.max(0,(n.now()-Number(e.started_at??0))/1e3),s=Math.min(1e3,Math.exp(ne*t));return Math.max(s,Number(e.multiplier??1))}return e.status==="cashed"?Number(e.multiplier??1):e.status==="crashed"?Number(e.crash_point??e.multiplier??1):1}function Ee(e,n,t){let s=qe(n,t);e.number.textContent=`${s.toFixed(2)}×`,e.number.className=`crash-number ${n.status==="running"?"running":n.status==="cashed"?"cashed":n.status==="crashed"?"crashed":""}`,e.potential.textContent=L(Math.floor(Number(n.bet??0)*s)),Qe(e.canvas,n,s,t)}function Qe(e,n,t,s){let a=e.clientWidth||600,r=e.clientHeight||360,d=window.devicePixelRatio||1;(e.width!==Math.round(a*d)||e.height!==Math.round(r*d))&&(e.width=Math.round(a*d),e.height=Math.round(r*d));let i=e.getContext("2d");if(!i)return;i.setTransform(d,0,0,d,0,0),i.clearRect(0,0,a,r);let m=54,p=16,u=18,g=34,f=Math.max(1,a-m-p),w=Math.max(1,r-u-g),y=n.status==="running"?Math.max(.25,(s.now()-Number(n.started_at??0))/1e3):t>1?Math.log(t)/ne:1,v=Math.max(5,y*1.08),E=Math.max(2,Math.ceil(t*1.15*2)/2),S=b=>m+b/v*f,l=b=>u+w-(b-1)/(E-1)*w;i.strokeStyle="#242e38",i.fillStyle="#8a98a6",i.font="11px ui-monospace, monospace",i.lineWidth=1;for(let b=0;b<=4;b+=1){let T=1+(E-1)*b/4,C=l(T);i.beginPath(),i.moveTo(m,C),i.lineTo(a-p,C),i.stroke(),i.fillText(`${T.toFixed(1)}×`,6,C+4)}for(let b=0;b<=4;b+=1){let T=v*b/4,C=S(T);i.beginPath(),i.moveTo(C,u),i.lineTo(C,u+w),i.strokeStyle="#1b232c",i.stroke(),i.fillStyle="#5b6773",i.fillText(`${T.toFixed(0)}s`,C-10,r-12)}let c=n.status==="crashed"?"#e05a5a":n.status==="cashed"?"#4ec27b":"#e0b64a";i.beginPath();let h=180;for(let b=0;b<=h;b+=1){let T=y*b/h,C=Math.min(E,Math.exp(ne*T)),me=S(T),fe=l(C);b===0?i.moveTo(me,fe):i.lineTo(me,fe)}i.strokeStyle=c,i.lineWidth=2.4,i.stroke();let M=S(y),N=l(Math.min(E,t));i.beginPath(),i.arc(M,N,4.5,0,Math.PI*2),i.fillStyle=c,i.fill(),i.fillStyle=c,i.font="600 12px ui-monospace, monospace",i.fillText(`${t.toFixed(2)}×`,Math.min(M+8,a-70),Math.max(u+12,N-10))}async function et(e,n,t){let s=new Uint8Array(e.length/2);for(let u=0;u<s.length;u+=1)s[u]=Number.parseInt(e.slice(u*2,u*2+2),16);let a=await crypto.subtle.digest("SHA-256",s),d=[...new Uint8Array(a)].map(u=>u.toString(16).padStart(2,"0")).join("")===n.toLowerCase(),i=0;for(let u=0;u<8;u+=1)i=i*256+s[u];i/=2**64;let m=Math.max(1,Math.min(1e3,(1-.01)/(1-i))),p=Math.abs(m-t)<1e-6;return[d,p]}function tt(e){let n=e.details??{};switch(e.game){case"slots":return String(n.win_type??"spin");case"roulette":return`${n.number??"?"} ${n.color??""}`.trim();case"blackjack":return String(n.result??"");case"crash":{let t=String(n.result??""),s=n.crash_point;return s==null?t:`${t} @ ${Number(s).toFixed(2)}×`}default:return""}}function Ne(e,n){H(e);let t=n.records??[];if(t.length===0){let m=o("div","board");m.append(o("h2",void 0,"No plays yet"),o("p","muted","Your settled plays will appear here.")),e.append(m);return}let s=o("div","table-wrap"),a=o("table"),r=o("thead"),d=o("tr");for(let m of["Time","Game","Bet","Result","Net","Balance"])d.append(o("th",void 0,m));r.append(d);let i=o("tbody");for(let m of t){let p=o("tr");p.append(o("td","mono",ye(String(m.at??"")))),p.append(o("td",void 0,String(m.game??""))),p.append(o("td","num",L(Number(m.bet??0)))),p.append(o("td","muted",tt(m)));let u=Number(m.winnings??0)-Number(m.bet??0),g=o("td",`num ${u>0?"pos":u<0?"neg":""}`,u===0?"0 Kr":he(u));p.append(g),p.append(o("td","num",L(Number(m.balance_after??0)))),i.append(p)}a.append(r,i),s.append(a),e.append(s)}var oe=null,He=0,nt="#b0313a",ot="#151b21",Be="#106b45",st="#0d5c39",rt="#09472c",at="#1f7d52",it="#ecf7f0",lt="#e0b64a";function ct(e){return e===0?"green":[1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36].includes(e)?"red":"black"}function dt(e,n){let s=Math.max(38,e*.055),a=Math.max(34,e*.05),r=Math.max(30,n*.07),d=6,i=e-28-s-a,m=i/12,p=(n-28-r*2-d*2)/3,u=14+s,g=14,f=g+p*3+d,w=f+r+d,y=[];for(let l=0;l<12;l+=1)for(let c=0;c<3;c+=1){let h=l*3+(3-c);y.push({x:u+l*m,y:g+c*p,w:m,h:p,target:{bet:"straight",number:h},label:String(h),kind:"number",number:h})}y.push({x:14,y:g,w:s,h:p*3,target:{bet:"straight",number:0},label:"0",kind:"zero",number:0});for(let l=0;l<3;l+=1){let c=2-l;y.push({x:u+i,y:g+l*p,w:a,h:p,target:{bet:"column",index:c},label:"2:1",kind:"column"})}for(let l=0;l<3;l+=1)y.push({x:u+i/3*l,y:f,w:i/3,h:r,target:{bet:"dozen",index:l},label:["1st 12","2nd 12","3rd 12"][l],kind:"dozen"});[{kind:"low",label:"1–18"},{kind:"even",label:"EVEN"},{kind:"red",label:"RED"},{kind:"black",label:"BLACK"},{kind:"odd",label:"ODD"},{kind:"high",label:"19–36"}].forEach((l,c)=>{y.push({x:u+i/6*c,y:w,w:i/6,h:r,target:{bet:"outside",kind:l.kind},label:l.label,kind:"outside"})});let E=[],S=l=>{let c=y.find(h=>h.kind==="number"&&h.number===l);return c?{x:c.x+c.w/2,y:c.y+c.h/2}:null};for(let l=0;l<11;l+=1)for(let c=0;c<2;c+=1){let h=l*3+(3-c);E.push({x:u+(l+1)*m,y:g+(c+1)*p,radius:12,rank:0,target:{bet:"corner",numbers:[h,h-1,h+3,h+2]}})}for(let l=0;l<12;l+=1)for(let c=0;c<2;c+=1){let h=l*3+(3-c),M=S(h);M&&E.push({x:M.x,y:g+(c+1)*p,radius:10,rank:1,target:{bet:"split",a:h,b:h-1}})}for(let l=0;l<11;l+=1)for(let c=0;c<3;c+=1){let h=l*3+(3-c);E.push({x:u+(l+1)*m,y:g+c*p+p/2,radius:10,rank:1,target:{bet:"split",a:h,b:h+3}})}for(let l=0;l<3;l+=1){let c=3-l;E.push({x:u,y:g+l*p+p/2,radius:10,rank:1,target:{bet:"split",a:0,b:c}})}return{width:e,height:n,cellW:m,cellH:p,zeroX:14,zeroW:s,gridX:u,gridY:g,gridW:i,columnX:u+i,dozensY:f,outsideY:w,outsideH:r,zones:y,hotspots:E}}function Re(e,n,t,s,a,r){let d=Math.min(r,s/2,a/2);e.beginPath(),e.moveTo(n+d,t),e.arcTo(n+s,t,n+s,t+a,d),e.arcTo(n+s,t+a,n,t+a,d),e.arcTo(n,t+a,n,t,d),e.arcTo(n,t,n+s,t,d),e.closePath()}function $e(e,n){let t=e.clientWidth||800,s=e.clientHeight||540,a=window.devicePixelRatio||1;(e.width!==Math.round(t*a)||e.height!==Math.round(s*a))&&(e.width=Math.round(t*a),e.height=Math.round(s*a));let r=e.getContext("2d"),d=dt(t,s);if(!r)return d;r.setTransform(a,0,0,a,0,0),r.clearRect(0,0,t,s),r.fillStyle=rt,Re(r,0,0,t,s,10),r.fill();for(let i of d.zones){let m=st;i.kind==="number"?m=i.number===0?Be:ct(i.number)==="red"?nt:ot:i.kind==="zero"&&(m=Be),r.fillStyle=m,r.strokeStyle=at,r.lineWidth=1,Re(r,i.x+1,i.y+1,i.w-2,i.h-2,3),r.fill(),r.stroke(),r.fillStyle=it,r.textAlign="center",r.textBaseline="middle",i.kind==="number"||i.kind==="zero"?r.font=`700 ${Math.min(24,d.cellH*.3)}px ui-monospace, monospace`:r.font=`600 ${Math.min(12,d.outsideH*.4)}px ui-monospace, monospace`,r.fillText(i.label,i.x+i.w/2,i.y+i.h/2)}return pt(r,d,n),d}function Ae(e){return JSON.stringify(e)}function ut(e,n){let t=Ae(n),s=e.zones.find(a=>Ae(a.target)===t);if(s)return{x:s.x+s.w/2,y:s.y+s.h/2};if(n?.bet==="split"||n?.bet==="corner"){let a=n.bet==="split"?[n.a,n.b]:n.numbers,r=e.zones.filter(d=>(d.kind==="number"||d.kind==="zero")&&a.includes(d.number));if(r.length>0)return{x:r.reduce((d,i)=>d+i.x+i.w/2,0)/r.length,y:r.reduce((d,i)=>d+i.y+i.h/2,0)/r.length}}return null}function pt(e,n,t){let s=t.bets?.chips??[];for(let a of s){let r=ut(n,a.target);if(!r)continue;let d=Math.max(12,Math.min(18,n.cellW*.26));e.beginPath(),e.arc(r.x,r.y,d,0,Math.PI*2),e.fillStyle=lt,e.fill(),e.strokeStyle="#7a5a12",e.lineWidth=2,e.stroke(),e.fillStyle="#221a05",e.textAlign="center",e.textBaseline="middle",e.font=`700 ${d>15?11:10}px ui-monospace, monospace`,e.fillText(String(a.amount),r.x,r.y+.5)}}function De(e,n,t,s){H(e),H(n);let a=t.phase==="spinning";e.append(o("h2",void 0,"Roulette")),e.append(o("p","note","European single-zero wheel. Click a number, line or corner to add a chip; right-click takes one back."));let r=o("div","roulette-wrap"),d=o("canvas","roulette-canvas");if(r.append(d),t.phase==="result"&&t.number!==null&&t.number!==void 0){let l=o("div",`wheel-result ${t.color??""}`);l.append(o("div","label","Result")),l.append(o("div","num",String(t.number)));let c=Number(t.winnings??0);l.append(o("div","note",c>0?`Won ${L(c)}`:"No win")),r.append(l)}e.append(r);let m=$e(d,t),p=(l,c)=>{let h=d.getBoundingClientRect(),M=l-h.left,N=c-h.top,b=null;for(let T of m.hotspots){let C=Math.hypot(M-T.x,N-T.y);C>T.radius||(!b||T.rank<b.rank||T.rank===b.rank&&C<Math.hypot(M-b.x,N-b.y))&&(b=T)}if(b)return{x:b.x-b.radius,y:b.y-b.radius,w:b.radius*2,h:b.radius*2,target:b.target,label:"",kind:"number"};for(let T=m.zones.length-1;T>=0;T-=1){let C=m.zones[T];if(M>=C.x&&M<=C.x+C.w&&N>=C.y&&N<=C.y+C.h)return C}return null};d.addEventListener("click",l=>{if(a)return;let c=p(l.clientX,l.clientY);c&&s({PlaceBetAt:c.target})}),d.addEventListener("contextmenu",l=>{if(l.preventDefault(),a)return;let c=p(l.clientX,l.clientY);c&&s({RemoveBetAt:c.target})}),d.addEventListener("mousemove",l=>{let c=p(l.clientX,l.clientY);d.style.cursor=c&&!a?"pointer":"default"}),oe?.disconnect(),oe=new ResizeObserver(()=>{m=$e(d,t)}),oe.observe(d),n.append(o("div","label","Roulette")),n.append(o("p","note","Chips sit on the felt. The total is reserved when you spin.")),n.append(o("div","label","Chip"));let u=o("div","chip-row");for(let l of[10,25,50,100,500]){let c=o("button",`chip-btn ${Number(t.chip)===l?"active":""}`,String(l));c.disabled=a,c.addEventListener("click",()=>s({SetChip:l})),u.append(c)}n.append(u);let g=(t.bets?.chips??[]).reduce((l,c)=>l+Number(c.amount??0),0),f=o("div","stat");if(f.append(o("span","muted","On the felt"),o("strong","mono",L(g))),n.append(f),t.balance!==null&&t.balance!==void 0){let l=o("div","stat");l.append(o("span","muted","Balance"),o("strong","mono",L(Number(t.balance)))),n.append(l)}t.error&&n.append(o("p","error-text",String(t.error)));let w=o("p","note");if(a)w.textContent="Spinning…";else if(t.phase==="result"){let l=Number(t.winnings??0);w.textContent=`${t.number} ${t.color} · ${l>0?`won ${L(l)}`:"no win"}`,w.className=l>0?"note win-text":"note"}else w.textContent=g>0?"Ready to spin.":"Place at least one chip.";n.append(w);let y=o("div","actions"),v=o("button","btn primary wide big",a?"SPINNING…":"SPIN WHEEL");v.disabled=a||g===0,v.addEventListener("click",()=>{Date.now()-He<600||(He=Date.now(),s("Spin"))});let E=o("button","btn wide","Clear bets");E.disabled=a||g===0,E.addEventListener("click",()=>s("ClearBets"));let S=o("button","btn ghost wide","Leave table");S.disabled=a,S.addEventListener("click",()=>{a||s("Leave")}),y.append(v,E,S),n.append(y)}var se=5,re=3,F="CLOGDS",mt=["top","middle","bottom","V","inverted V"],K=0,Pe=0,A,X=null,G=null;function ft(){K=Date.now()+900}function We(){A!==void 0&&(window.clearInterval(A),A=void 0),X=null,G=null,K=0}function ht(e){return e.phase==="spinning"||Date.now()<K}function ae(e,n,t,s){X=t,G=e,A!==void 0&&(window.clearInterval(A),A=void 0);let a=ht(t);H(e),H(n);let r=o("h2",void 0,"Slots"),d=o("p","note","Match three or more symbols from the left. Five paylines.");e.append(r,d);let i=o("div","reels"),m=[],p=Array.isArray(t.reels)?t.reels.join(""):String(t.reels??"");for(let c=0;c<se;c+=1){let h=o("div","reel");for(let M=0;M<re;M+=1){let N=c*re+M,b=a?F[(N*3+Math.floor(Date.now()/70))%F.length]:p.length===se*re?p[N]:p.length===se?p[c]:F[(N*5+c)%F.length],T=o("div","tile",b);T.classList.add(`sym-${b}`),a&&T.classList.add("spinning"),!a&&(t.win_cells??[]).includes(N)&&T.classList.add("win"),m.push(T),h.append(T)}i.append(h)}e.append(i);let u=o("div","win-lines");if(!a&&t.phase==="result"){let c=Number(t.winnings??0);if(c>0){let h=(t.win_lines??[]).map(M=>`${M} (${mt[M-1]??"line"})`).join(" · ");u.textContent=`${t.win_type==="mega"?"MEGA WIN":t.win_type==="big"?"BIG WIN":"WIN"} ${L(c)}${h?` · ${h}`:""}`,u.classList.add("win-text")}else u.textContent="No win — spin again."}else a?u.textContent="Spinning…":u.textContent="Pick a bet and spin.";if(e.append(u),a){let c=0;A=window.setInterval(()=>{c+=1;for(let h=0;h<m.length;h+=1){let M=F[(h*3+c)%F.length];m[h].textContent=M,m[h].className=`tile spinning sym-${M}`}Date.now()>=K&&X&&G&&(window.clearInterval(A),A=void 0,ae(G,n,X,s))},70)}n.append(o("div","label","Slots")),n.append(o("p","note","5 reels × 3 rows, 5 paylines."));let g=o("div","row"),f=o("button","btn small","−10"),w=o("strong","mono",L(Number(t.bet??0))),y=o("button","btn small","+10");f.disabled=a,y.disabled=a,f.addEventListener("click",()=>s({AdjustBet:-10})),y.addEventListener("click",()=>s({AdjustBet:10})),g.append(f,w,y),n.append(o("div","label","Bet"),g),t.error&&n.append(o("p","error-text",String(t.error)));let v=o("p","note");if(a)v.textContent="Spinning…";else if(t.phase==="result"){let c=Number(t.winnings??0);v.textContent=c>0?`Won ${L(c)}`:"No win this spin.",c>0&&v.classList.add("win-text")}else v.textContent="Ready when you are.";n.append(v);let E=o("div","actions"),S=o("button","btn primary wide big",a?"SPINNING…":"SPIN");S.disabled=a,S.addEventListener("click",()=>{Date.now()-Pe<900||(Pe=Date.now(),ft(),s("Spin"))});let l=o("button","btn ghost wide","Leave machine");l.disabled=a,l.addEventListener("click",()=>s("Leave")),E.append(S,l),n.append(E)}var bt=k("auth-screen"),gt=k("lobby-screen"),yt=k("game-screen"),kt=k("history-screen"),Ie=k("session-bar"),Lt=k("account-name"),xt=k("account-balance"),ie=k("game-grid"),U=k("game-board"),Z=k("game-side"),wt=k("game-title"),ze=k("game-badge"),vt=k("history-body"),Tt=k("auth-form"),Ve=k("auth-user"),q=k("auth-pass"),O=k("auth-error"),P=k("auth-submit"),_e=k("tab-login"),je=k("tab-register"),J=k("reconnect-overlay"),Fe=k("reconnect-message"),Oe=k("replaced-overlay"),x=null,D=null,ue=null,_="login",le=null,Ye=0,ce=0,de=new Map([["slots",10],["roulette",10],["blackjack",10],["crash",10]]),Xe={slots:"Slots",roulette:"Roulette",blackjack:"Blackjack",crash:"Crash"},Et={slots:"Five reels, five paylines. Match symbols from the left.",roulette:"European single-zero wheel with inside and outside bets.",blackjack:"Six-deck shoe. Blackjack pays 3:2.",crash:"A rising multiplier. Cash out before it crashes."},Mt=location.pathname.includes("/play/")?location.pathname.slice(0,location.pathname.indexOf("/play/")):"",Ct=`${location.protocol==="https:"?"wss":"ws"}://${location.host}${Mt}/ws`,Q=new j(Ct,{onHello(e){Ye=e.time-Date.now(),D=e.user??null,ze.textContent=`v${e.version}`},onView(e){x=e,e.screen!=="slots"&&We(),e.screen!=="crash"&&Ce(),typeof e.balance=="number"&&(ue=e.balance),e.screen==="lobby"&&"user"in e&&(D=e.user??null,D?q.value="":ue=null),ce=0,J.hidden=!0,Ge(),D&&x.balance===null&&le!==D&&(le=D,Q.send("Info")),D||(le=null)},onNotice(e){e.startsWith("Connected to:")||e.startsWith("Fingerprint:")||e.startsWith("User:")||e.startsWith("Account:")||e.startsWith("Balance:")||(ee(e,be(e)),e.startsWith("Account created")&&(pe("login"),q.value="",Ve.focus()),P.disabled=!1,P.textContent=_==="login"?"Log in":"Create account")},onError(e){ee(e,"bad"),P.disabled=!1,P.textContent=_==="login"?"Log in":"Create account"},onReplaced(){Oe.hidden=!1},onPong(){},onOpen(){J.hidden=!0},onClosed(e){if(Oe.hidden!==!1){if(!e){Fe.textContent="The connection could not be restored. Reload the page to try again.",J.hidden=!1;return}ce+=1,Fe.textContent=ce>1?"Still trying to reach the game server…":"Trying to reach the game server…",J.hidden=!1}}});function B(e){Q.send(e)}function St(){return Date.now()+Ye}function pe(e){_=e,_e.classList.toggle("active",e==="login"),je.classList.toggle("active",e==="register"),P.textContent=e==="login"?"Log in":"Create account",q.autocomplete=e==="login"?"current-password":"new-password",O.textContent=""}function V(e){bt.hidden=e!=="auth",gt.hidden=e!=="lobby",yt.hidden=e!=="game",kt.hidden=e!=="history"}function Nt(e){xt.textContent=L(e??null)}function Ge(){if(!x)return;let e=D;if(e?(Ie.hidden=!1,Lt.textContent=String(e)):Ie.hidden=!0,Nt(ue),!e){V("auth"),O.textContent=x.login_error?String(x.login_error):"",x.screen==="lobby"&&x.connected===!1&&(O.textContent="The game server is not reachable yet. Retrying…");return}switch(x.screen){case"lobby":V("lobby"),Bt(x);break;case"history":V("history"),Ne(vt,x);break;case"slots":case"roulette":case"blackjack":case"crash":V("game"),wt.textContent=Xe[x.screen]??x.screen,ze.textContent=Ht(x),x.screen==="slots"?ae(U,Z,x,B):x.screen==="roulette"?De(U,Z,x,B):x.screen==="blackjack"?we(U,Z,x,B):Se(U,Z,x,B,{now:St});break;default:V("auth")}}function Ht(e){return e.screen==="crash"||e.screen==="blackjack"?String(e.status??""):e.screen==="slots"||e.screen==="roulette"?String(e.phase??""):""}function Bt(e){ie.replaceChildren();let n=e.games??[];for(let[t,s]of n){let a=o("article",`game-card ${t}`);a.append(o("h3",void 0,s||Xe[t]||t)),a.append(o("p",void 0,Et[t]??"Play a round."));let r=o("div","row wrap"),d=o("label","field");d.style.flex="1",d.append(o("span","label","Bet"));let i=o("input","input bet-input");i.type="number",i.min="1",i.step="10",i.value=String(de.get(t)??10),i.addEventListener("change",()=>{let p=Math.max(1,Math.floor(Number(i.value)||1));i.value=String(p),de.set(t,p)}),d.append(i);let m=o("button","btn primary",t==="roulette"?"Open table":"Play");m.addEventListener("click",()=>{let p=Math.max(1,Math.floor(Number(i.value)||1));de.set(t,p),B({Play:{game:t,bet:p}})}),r.append(d,m),a.append(r),ie.append(a)}n.length===0&&ie.append(o("p","muted","The server did not advertise any games."))}_e.addEventListener("click",()=>pe("login"));je.addEventListener("click",()=>pe("register"));Tt.addEventListener("submit",async e=>{e.preventDefault();let n=Ve.value.trim(),t=q.value;if(!n||!t){O.textContent="Enter a username and a password.";return}P.disabled=!0,P.textContent="Working…",O.textContent="";try{let s=await ge(t);B(_==="login"?{Login:{user:n,credential:s}}:{Register:{user:n,credential:s}})}catch{O.textContent="This browser cannot hash the password (needs a secure context).",P.disabled=!1,P.textContent=_==="login"?"Log in":"Create account"}});k("logout").addEventListener("click",()=>B("Logout"));k("show-history").addEventListener("click",()=>B({History:{limit:50}}));k("refresh-lobby").addEventListener("click",()=>B("Info"));k("history-back").addEventListener("click",()=>B("ShowLobby"));k("leave-game").addEventListener("click",()=>{!x||(x.screen==="blackjack"&&x.status==="player"||x.screen==="crash"&&x.status==="running")&&!window.confirm("Leaving now forfeits the active bet. Continue?")||B("Leave")});k("reload-replaced").addEventListener("click",()=>location.reload());k("reload-reconnect").addEventListener("click",()=>location.reload());window.addEventListener("resize",()=>{window.clearTimeout(window.__resizeTimer),window.__resizeTimer=window.setTimeout(()=>{x&&Ge()},140)});window.setInterval(()=>{D&&Q.ping()},25e3);Q.connect();})(); "use strict";(()=>{function k(e){let n=document.getElementById(e);if(!n)throw new Error(`missing #${e}`);return n}function o(e,n,t){let s=document.createElement(e);return n&&(s.className=n),t!==void 0&&(s.textContent=t),s}function H(e){e.replaceChildren()}function Ke(e){let n=e<0,t=String(Math.abs(Math.trunc(e))),s="";for(let a=0;a<t.length;a+=1)a>0&&(t.length-a)%3===0&&(s+=" "),s+=t[a];return n?`-${s}`:s}function L(e){return e==null||Number.isNaN(e)?"— Kr":`${Ke(e)} Kr`}function he(e){return`${e<0?"-":"+"}${L(Math.abs(e))}`}function te(e,n="info"){let t=document.getElementById("toasts");if(!t||!e)return;let s=o("div",`toast ${n}`,e);for(t.append(s),window.setTimeout(()=>{s.style.opacity="0",s.style.transition="opacity 220ms ease",window.setTimeout(()=>s.remove(),240)},4200);t.children.length>4;)t.firstElementChild?.remove()}function be(e){let n=e.toLowerCase();return e.startsWith("ERROR")||n.includes("failed")||n.includes("lost")||n.includes("invalid")||n.includes("forfeit")?"bad":e.startsWith("Logged in")||e.startsWith("Account created")||n.includes("won")||n.includes("cashed")?"good":"info"}async function ge(e){let n=new TextEncoder().encode(e),t=await crypto.subtle.digest("SHA-256",n);return[...new Uint8Array(t)].map(s=>s.toString(16).padStart(2,"0")).join("")}function z(e,n=10){return e.length<=n*2?e:`${e.slice(0,n)}…${e.slice(-n)}`}function ye(e){let n=Date.parse(e.includes("T")?e:e.replace(" ","T")+"Z");return Number.isNaN(n)?e:new Date(n).toLocaleString()}function ke(e){if(e==="?"||!e)return{rank:"?",suit:"",red:!1};let n=e.slice(-1);return{rank:e.slice(0,-1),suit:n==="C"?"♣":n==="D"?"♦":n==="H"?"♥":n==="S"?"♠":n,red:n==="D"||n==="H"}}var j=class{constructor(n,t){this.url=n;this.handlers=t;this.socket=null;this.stopped=!1;this.replaced=!1;this.attempts=0}connect(){if(this.stopped)return;this.clearTimer();let n;try{n=new WebSocket(this.url)}catch{this.scheduleReconnect()||this.handlers.onClosed(!1);return}this.socket=n,n.addEventListener("open",()=>{this.attempts=0,this.handlers.onOpen()}),n.addEventListener("message",t=>{let s;try{s=JSON.parse(String(t.data))}catch{return}switch(s.type){case"hello":this.handlers.onHello(s);break;case"view":this.handlers.onView(s.view);break;case"notice":this.handlers.onNotice(String(s.text??""));break;case"error":this.handlers.onError(String(s.text??""));break;case"replaced":this.replaced=!0,this.handlers.onReplaced();break;case"pong":this.handlers.onPong();break}}),n.addEventListener("close",()=>{if(this.socket===n&&(this.socket=null),this.replaced||this.stopped){this.handlers.onClosed(!1);return}let s=this.scheduleReconnect();this.handlers.onClosed(s)}),n.addEventListener("error",()=>{})}send(n){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({type:"command",command:n}))}ping(){this.socket?.readyState===WebSocket.OPEN&&this.socket.send(JSON.stringify({type:"ping"}))}close(){this.stopped=!0,this.clearTimer(),this.socket?.close()}scheduleReconnect(){if(this.stopped||this.replaced||this.attempts>=6)return!1;this.attempts+=1;let n=Math.min(250*2**(this.attempts-1),4e3);return this.timer=window.setTimeout(()=>this.connect(),n),!0}clearTimer(){this.timer!==void 0&&(window.clearTimeout(this.timer),this.timer=void 0)}};var Le=0;function ne(){return Date.now()-Le<400?!1:(Le=Date.now(),!0)}function Ue(e){if(e==="?"){let s=o("div","card back");return s.append(o("span","rank","?"),o("span","suit","")),s}let n=ke(e),t=o("div","card");return n.red&&t.classList.add("red"),t.append(o("span","rank",n.rank),o("span","suit",n.suit)),t}function xe(e,n,t){let s=o("div","hand");s.append(o("div","label",e));let a=o("div","cards");for(let d of n)a.append(Ue(d));let r=o("div","total");return r.append(document.createTextNode("Total "),o("strong",void 0,String(t))),s.append(a,r),s}function Ze(e,n){let t=String(n?.result??""),s=Number(n?.winnings??0);switch(t){case"blackjack":return`Blackjack — won ${L(s)}`;case"win":return`You win — ${L(s)}`;case"push":return`Push — ${L(s)} returned`;case"bust":return"Bust — the bet is lost";case"lose":return"Dealer wins — the bet is lost";case"forfeit":return"Hand forfeited — the bet is lost";case"playing":return e.status==="player"?"Your move.":"Dealing…";default:return"Ready to deal."}}function we(e,n,t,s){H(e),H(n);let a=t.hand,r=a?.phase==="player"||t.status==="player",d=a?.phase==="done"||t.status==="done",i=!a||d;if(e.append(o("h2",void 0,"Blackjack")),e.append(o("p","note","Six decks. Dealer stands on all 17. Blackjack pays 3:2.")),a)e.append(xe("Dealer",a.dealer??[],Number(a.dealer_total??0))),e.append(xe("You",a.player??[],Number(a.player_total??0)));else{let f=o("div","outcome");f.textContent="Press DEAL to start a hand.",e.append(f)}if(d||a&&!r){let f=o("div","outcome");f.textContent=Ze(t,a),(String(a?.result)==="win"||String(a?.result)==="blackjack"||String(a?.result)==="push")&&f.classList.add("win-text"),e.append(f)}t.error&&e.append(o("p","error-text",String(t.error))),n.append(o("div","label","Blackjack"));let m=Number(a?.bet??t.bet??0),p=t.balance??t.hand?.balance;if(n.append(o("p","note","Hit, stand or double. Forfeiting loses the reserved bet.")),i){let f=o("div","row"),w=o("button","btn small","−10"),y=o("strong","mono",L(m)),v=o("button","btn small","+10");w.addEventListener("click",()=>s({AdjustBet:-10})),v.addEventListener("click",()=>s({AdjustBet:10})),f.append(w,y,v),n.append(o("div","label","Bet"),f)}else{let f=o("div","stat");f.append(o("span","muted","Bet"),o("strong",void 0,L(m))),n.append(f)}if(p!==void 0){let f=o("div","stat");f.append(o("span","muted","Balance"),o("strong",void 0,L(Number(p)))),n.append(f)}let u=o("div","actions");if(r){let f=o("div","row wrap");for(let y of a?.actions??[]){let v=y==="hit"?"HIT":y==="stand"?"STAND":"DOUBLE",E=y==="stand"?o("button","btn primary",v):o("button","btn",v);E.addEventListener("click",()=>{ne()&&s(y==="hit"?"Hit":y==="stand"?"Stand":"Double")}),f.append(E)}u.append(f);let w=o("button","btn danger wide","Forfeit hand");w.addEventListener("click",()=>{ne()&&window.confirm("Forfeit this hand? The reserved bet is lost.")&&s("Forfeit")}),u.append(w)}else if(i){let f=o("button","btn primary wide big","DEAL");f.addEventListener("click",()=>{ne()&&s("Deal")}),u.append(f)}let g=o("button","btn ghost wide",r?"Leave (forfeits)":"Leave table");g.addEventListener("click",()=>{r&&!window.confirm("Leaving forfeits this hand. Continue?")||s("Leave")}),u.append(g),n.append(u)}var oe=Math.LN2/5,R=null,I=null,Me=null,W=null,$,ve=0,Y="",Te=0;function Je(e,n,t,s){H(e),H(n),W=t,Me=s,e.append(o("h2",void 0,"Crash")),e.append(o("p","note","The multiplier rises from 1.00×. Cash out before it crashes."));let a=o("div","crash-wrap"),r=o("div","crash-number","1.00×"),d=o("div","crash-meta"),i=o("canvas","crash-canvas"),m=o("div","verify","Waiting for a round commitment.");a.append(r,d,i,m),e.append(a),n.append(o("div","label","Crash")),n.append(o("p","note","The server commits to a seed before the round. The seed is revealed when it ends."));let p=o("div","row"),u=o("button","btn small","−10"),g=o("strong","mono","—"),f=o("button","btn small","+10");u.addEventListener("click",()=>W?.({AdjustBet:-10})),f.addEventListener("click",()=>W?.({AdjustBet:10})),p.append(u,g,f),n.append(o("div","label","Bet"),p);let w=o("strong","mono","—"),y=o("strong","mono","—");for(let[M,N]of[["Potential",w],["Balance",y]]){let b=o("div","stat");b.append(o("span","muted",M),N),n.append(b)}let v=o("p","note","Ready."),E=o("p","note",""),S=o("p","error-text");n.append(v,E,S);let l=o("div","actions"),c=o("button","btn primary wide big","START"),h=o("button","btn ghost wide","Leave table");return c.addEventListener("click",()=>{Date.now()-Te<500||(Te=Date.now(),I?.status==="running"?W?.("CashOut"):W?.("Start"))}),h.addEventListener("click",()=>{I?.status==="running"&&!window.confirm("Forfeit this round? The bet is lost.")||W?.("Leave")}),l.append(c,h),n.append(l),{board:e,side:n,number:r,meta:d,verify:m,canvas:i,bet:g,potential:w,balance:y,status:v,lastCrash:E,error:S,primary:c,leave:h,minus:u,plus:f}}function Ce(){$!==void 0&&(cancelAnimationFrame($),$=void 0),I=null}function Se(e,n,t,s,a){(!R||R.board!==e||!R.canvas.isConnected)&&(R=Je(e,n,s,a),Y="");let r=R;W=s,Me=a,I=t,t.status!==Y&&((t.status==="starting"||t.status==="running")&&Y!=="running"&&(r.verify.className="verify",r.verify.textContent="Round committed; seed hidden until the round ends."),Y=String(t.status));let d=t.status==="running",i=t.status==="starting",m=d||i;r.bet.textContent=L(Number(t.bet??0)),r.potential.textContent=L(Number(t.potential??t.bet??0)),r.balance.textContent=t.balance===null||t.balance===void 0?"—":L(Number(t.balance)),r.error.textContent=t.error?String(t.error):"",r.minus.disabled=m,r.plus.disabled=m,r.primary.textContent=d?"CASH OUT":i?"STARTING…":"START",r.primary.disabled=i,r.primary.className="btn primary wide big",r.leave.textContent=d?"Forfeit round":"Leave table",d?(r.status.textContent="Rising — cash out before the crash.",r.status.className="note win-text"):i?(r.status.textContent="Starting the round…",r.status.className="note"):t.status==="cashed"?(r.status.textContent=`Cashed out at ${Number(t.multiplier??0).toFixed(2)}× for ${L(Number(t.payout??0))}.`,r.status.className="note win-text"):t.status==="crashed"?(r.status.textContent=`Crashed at ${Number(t.crash_point??t.multiplier??0).toFixed(2)}×. The bet is lost.`,r.status.className="note error-text"):t.status==="forfeit"?(r.status.textContent="Round forfeited.",r.status.className="note error-text"):(r.status.textContent="Ready. Start a round when you want.",r.status.className="note"),t.crash_point!==null&&t.crash_point!==void 0&&t.status==="crashed"&&(r.lastCrash.textContent=`Last crash: ${Number(t.crash_point).toFixed(2)}×`);let p=t.commit?String(t.commit):"";if(r.meta.textContent=p?`commit ${z(p,12)}`:"no commitment yet",t.seed&&p){let u=++ve,g=String(t.seed),f=Number(t.crash_point??0);et(g,p,f).then(([w,y])=>{if(u!==ve||!R)return;let v=w&&y;R.verify.className=`verify ${v?"ok":"bad"}`,R.verify.textContent=v?`Seed ${z(g,12)} · sha256 matches commit · crash point recomputed`:`VERIFICATION FAILED · seed ${z(g,12)} · commit ${z(p,12)}`})}else!m&&!p&&(r.verify.className="verify",r.verify.textContent="Waiting for a round commitment.");if($!==void 0&&(cancelAnimationFrame($),$=void 0),d){let u=()=>{if(!R||I?.status!=="running"){$=void 0;return}Ee(R,I,a),$=requestAnimationFrame(u)};$=requestAnimationFrame(u)}else Ee(r,t,a)}function qe(e,n){if(e.status==="running"){let t=Math.max(0,(n.now()-Number(e.started_at??0))/1e3),s=Math.min(1e3,Math.exp(oe*t));return Math.max(s,Number(e.multiplier??1))}return e.status==="cashed"?Number(e.multiplier??1):e.status==="crashed"?Number(e.crash_point??e.multiplier??1):1}function Ee(e,n,t){let s=qe(n,t);e.number.textContent=`${s.toFixed(2)}×`,e.number.className=`crash-number ${n.status==="running"?"running":n.status==="cashed"?"cashed":n.status==="crashed"?"crashed":""}`,e.potential.textContent=L(Math.floor(Number(n.bet??0)*s)),Qe(e.canvas,n,s,t)}function Qe(e,n,t,s){let a=e.clientWidth||600,r=e.clientHeight||360,d=window.devicePixelRatio||1;(e.width!==Math.round(a*d)||e.height!==Math.round(r*d))&&(e.width=Math.round(a*d),e.height=Math.round(r*d));let i=e.getContext("2d");if(!i)return;i.setTransform(d,0,0,d,0,0),i.clearRect(0,0,a,r);let m=54,p=16,u=18,g=34,f=Math.max(1,a-m-p),w=Math.max(1,r-u-g),y=n.status==="running"?Math.max(.25,(s.now()-Number(n.started_at??0))/1e3):t>1?Math.log(t)/oe:1,v=Math.max(5,y*1.08),E=Math.max(2,Math.ceil(t*1.15*2)/2),S=b=>m+b/v*f,l=b=>u+w-(b-1)/(E-1)*w;i.strokeStyle="#242e38",i.fillStyle="#8a98a6",i.font="11px ui-monospace, monospace",i.lineWidth=1;for(let b=0;b<=4;b+=1){let T=1+(E-1)*b/4,C=l(T);i.beginPath(),i.moveTo(m,C),i.lineTo(a-p,C),i.stroke(),i.fillText(`${T.toFixed(1)}×`,6,C+4)}for(let b=0;b<=4;b+=1){let T=v*b/4,C=S(T);i.beginPath(),i.moveTo(C,u),i.lineTo(C,u+w),i.strokeStyle="#1b232c",i.stroke(),i.fillStyle="#5b6773",i.fillText(`${T.toFixed(0)}s`,C-10,r-12)}let c=n.status==="crashed"?"#e05a5a":n.status==="cashed"?"#4ec27b":"#e0b64a";i.beginPath();let h=180;for(let b=0;b<=h;b+=1){let T=y*b/h,C=Math.min(E,Math.exp(oe*T)),me=S(T),fe=l(C);b===0?i.moveTo(me,fe):i.lineTo(me,fe)}i.strokeStyle=c,i.lineWidth=2.4,i.stroke();let M=S(y),N=l(Math.min(E,t));i.beginPath(),i.arc(M,N,4.5,0,Math.PI*2),i.fillStyle=c,i.fill(),i.fillStyle=c,i.font="600 12px ui-monospace, monospace",i.fillText(`${t.toFixed(2)}×`,Math.min(M+8,a-70),Math.max(u+12,N-10))}async function et(e,n,t){let s=new Uint8Array(e.length/2);for(let u=0;u<s.length;u+=1)s[u]=Number.parseInt(e.slice(u*2,u*2+2),16);let a=await crypto.subtle.digest("SHA-256",s),d=[...new Uint8Array(a)].map(u=>u.toString(16).padStart(2,"0")).join("")===n.toLowerCase(),i=0;for(let u=0;u<8;u+=1)i=i*256+s[u];i/=2**64;let m=Math.max(1,Math.min(1e3,(1-.01)/(1-i))),p=Math.abs(m-t)<1e-6;return[d,p]}function tt(e){let n=e.details??{};switch(e.game){case"slots":return String(n.win_type??"spin");case"roulette":return`${n.number??"?"} ${n.color??""}`.trim();case"blackjack":return String(n.result??"");case"crash":{let t=String(n.result??""),s=n.crash_point;return s==null?t:`${t} @ ${Number(s).toFixed(2)}×`}default:return""}}function Ne(e,n){H(e);let t=n.records??[];if(t.length===0){let m=o("div","board");m.append(o("h2",void 0,"No plays yet"),o("p","muted","Your settled plays will appear here.")),e.append(m);return}let s=o("div","table-wrap"),a=o("table"),r=o("thead"),d=o("tr");for(let m of["Time","Game","Bet","Result","Net","Balance"])d.append(o("th",void 0,m));r.append(d);let i=o("tbody");for(let m of t){let p=o("tr");p.append(o("td","mono",ye(String(m.at??"")))),p.append(o("td",void 0,String(m.game??""))),p.append(o("td","num",L(Number(m.bet??0)))),p.append(o("td","muted",tt(m)));let u=Number(m.winnings??0)-Number(m.bet??0),g=o("td",`num ${u>0?"pos":u<0?"neg":""}`,u===0?"0 Kr":he(u));p.append(g),p.append(o("td","num",L(Number(m.balance_after??0)))),i.append(p)}a.append(r,i),s.append(a),e.append(s)}var se=null,He=0,nt="#b0313a",ot="#151b21",Be="#106b45",st="#0d5c39",rt="#09472c",at="#1f7d52",it="#ecf7f0",lt="#e0b64a";function ct(e){return e===0?"green":[1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36].includes(e)?"red":"black"}function dt(e,n){let s=Math.max(38,e*.055),a=Math.max(34,e*.05),r=Math.max(30,n*.07),d=6,i=e-28-s-a,m=i/12,p=(n-28-r*2-d*2)/3,u=14+s,g=14,f=g+p*3+d,w=f+r+d,y=[];for(let l=0;l<12;l+=1)for(let c=0;c<3;c+=1){let h=l*3+(3-c);y.push({x:u+l*m,y:g+c*p,w:m,h:p,target:{bet:"straight",number:h},label:String(h),kind:"number",number:h})}y.push({x:14,y:g,w:s,h:p*3,target:{bet:"straight",number:0},label:"0",kind:"zero",number:0});for(let l=0;l<3;l+=1){let c=2-l;y.push({x:u+i,y:g+l*p,w:a,h:p,target:{bet:"column",index:c},label:"2:1",kind:"column"})}for(let l=0;l<3;l+=1)y.push({x:u+i/3*l,y:f,w:i/3,h:r,target:{bet:"dozen",index:l},label:["1st 12","2nd 12","3rd 12"][l],kind:"dozen"});[{kind:"low",label:"1–18"},{kind:"even",label:"EVEN"},{kind:"red",label:"RED"},{kind:"black",label:"BLACK"},{kind:"odd",label:"ODD"},{kind:"high",label:"19–36"}].forEach((l,c)=>{y.push({x:u+i/6*c,y:w,w:i/6,h:r,target:{bet:"outside",kind:l.kind},label:l.label,kind:"outside"})});let E=[],S=l=>{let c=y.find(h=>h.kind==="number"&&h.number===l);return c?{x:c.x+c.w/2,y:c.y+c.h/2}:null};for(let l=0;l<11;l+=1)for(let c=0;c<2;c+=1){let h=l*3+(3-c);E.push({x:u+(l+1)*m,y:g+(c+1)*p,radius:12,rank:0,target:{bet:"corner",numbers:[h,h-1,h+3,h+2]}})}for(let l=0;l<12;l+=1)for(let c=0;c<2;c+=1){let h=l*3+(3-c),M=S(h);M&&E.push({x:M.x,y:g+(c+1)*p,radius:10,rank:1,target:{bet:"split",a:h,b:h-1}})}for(let l=0;l<11;l+=1)for(let c=0;c<3;c+=1){let h=l*3+(3-c);E.push({x:u+(l+1)*m,y:g+c*p+p/2,radius:10,rank:1,target:{bet:"split",a:h,b:h+3}})}for(let l=0;l<3;l+=1){let c=3-l;E.push({x:u,y:g+l*p+p/2,radius:10,rank:1,target:{bet:"split",a:0,b:c}})}return{width:e,height:n,cellW:m,cellH:p,zeroX:14,zeroW:s,gridX:u,gridY:g,gridW:i,columnX:u+i,dozensY:f,outsideY:w,outsideH:r,zones:y,hotspots:E}}function Re(e,n,t,s,a,r){let d=Math.min(r,s/2,a/2);e.beginPath(),e.moveTo(n+d,t),e.arcTo(n+s,t,n+s,t+a,d),e.arcTo(n+s,t+a,n,t+a,d),e.arcTo(n,t+a,n,t,d),e.arcTo(n,t,n+s,t,d),e.closePath()}function $e(e,n){let t=e.clientWidth||800,s=e.clientHeight||540,a=window.devicePixelRatio||1;(e.width!==Math.round(t*a)||e.height!==Math.round(s*a))&&(e.width=Math.round(t*a),e.height=Math.round(s*a));let r=e.getContext("2d"),d=dt(t,s);if(!r)return d;r.setTransform(a,0,0,a,0,0),r.clearRect(0,0,t,s),r.fillStyle=rt,Re(r,0,0,t,s,10),r.fill();for(let i of d.zones){let m=st;i.kind==="number"?m=i.number===0?Be:ct(i.number)==="red"?nt:ot:i.kind==="zero"&&(m=Be),r.fillStyle=m,r.strokeStyle=at,r.lineWidth=1,Re(r,i.x+1,i.y+1,i.w-2,i.h-2,3),r.fill(),r.stroke(),r.fillStyle=it,r.textAlign="center",r.textBaseline="middle",i.kind==="number"||i.kind==="zero"?r.font=`700 ${Math.min(24,d.cellH*.3)}px ui-monospace, monospace`:r.font=`600 ${Math.min(12,d.outsideH*.4)}px ui-monospace, monospace`,r.fillText(i.label,i.x+i.w/2,i.y+i.h/2)}return pt(r,d,n),d}function Ae(e){return JSON.stringify(e)}function ut(e,n){let t=Ae(n),s=e.zones.find(a=>Ae(a.target)===t);if(s)return{x:s.x+s.w/2,y:s.y+s.h/2};if(n?.bet==="split"||n?.bet==="corner"){let a=n.bet==="split"?[n.a,n.b]:n.numbers,r=e.zones.filter(d=>(d.kind==="number"||d.kind==="zero")&&a.includes(d.number));if(r.length>0)return{x:r.reduce((d,i)=>d+i.x+i.w/2,0)/r.length,y:r.reduce((d,i)=>d+i.y+i.h/2,0)/r.length}}return null}function pt(e,n,t){let s=t.bets?.chips??[];for(let a of s){let r=ut(n,a.target);if(!r)continue;let d=Math.max(12,Math.min(18,n.cellW*.26));e.beginPath(),e.arc(r.x,r.y,d,0,Math.PI*2),e.fillStyle=lt,e.fill(),e.strokeStyle="#7a5a12",e.lineWidth=2,e.stroke(),e.fillStyle="#221a05",e.textAlign="center",e.textBaseline="middle",e.font=`700 ${d>15?11:10}px ui-monospace, monospace`,e.fillText(String(a.amount),r.x,r.y+.5)}}function De(e,n,t,s){H(e),H(n);let a=t.phase==="spinning";e.append(o("h2",void 0,"Roulette")),e.append(o("p","note","European single-zero wheel. Click a number, line or corner to add a chip; right-click takes one back."));let r=o("div","roulette-wrap"),d=o("canvas","roulette-canvas");if(r.append(d),t.phase==="result"&&t.number!==null&&t.number!==void 0){let l=o("div",`wheel-result ${t.color??""}`);l.append(o("div","label","Result")),l.append(o("div","num",String(t.number)));let c=Number(t.winnings??0);l.append(o("div","note",c>0?`Won ${L(c)}`:"No win")),r.append(l)}e.append(r);let m=$e(d,t),p=(l,c)=>{let h=d.getBoundingClientRect(),M=l-h.left,N=c-h.top,b=null;for(let T of m.hotspots){let C=Math.hypot(M-T.x,N-T.y);C>T.radius||(!b||T.rank<b.rank||T.rank===b.rank&&C<Math.hypot(M-b.x,N-b.y))&&(b=T)}if(b)return{x:b.x-b.radius,y:b.y-b.radius,w:b.radius*2,h:b.radius*2,target:b.target,label:"",kind:"number"};for(let T=m.zones.length-1;T>=0;T-=1){let C=m.zones[T];if(M>=C.x&&M<=C.x+C.w&&N>=C.y&&N<=C.y+C.h)return C}return null};d.addEventListener("click",l=>{if(a)return;let c=p(l.clientX,l.clientY);c&&s({PlaceBetAt:c.target})}),d.addEventListener("contextmenu",l=>{if(l.preventDefault(),a)return;let c=p(l.clientX,l.clientY);c&&s({RemoveBetAt:c.target})}),d.addEventListener("mousemove",l=>{let c=p(l.clientX,l.clientY);d.style.cursor=c&&!a?"pointer":"default"}),se?.disconnect(),se=new ResizeObserver(()=>{m=$e(d,t)}),se.observe(d),n.append(o("div","label","Roulette")),n.append(o("p","note","Chips sit on the felt. The total is reserved when you spin.")),n.append(o("div","label","Chip"));let u=o("div","chip-row");for(let l of[10,25,50,100,500]){let c=o("button",`chip-btn ${Number(t.chip)===l?"active":""}`,String(l));c.disabled=a,c.addEventListener("click",()=>s({SetChip:l})),u.append(c)}n.append(u);let g=(t.bets?.chips??[]).reduce((l,c)=>l+Number(c.amount??0),0),f=o("div","stat");if(f.append(o("span","muted","On the felt"),o("strong","mono",L(g))),n.append(f),t.balance!==null&&t.balance!==void 0){let l=o("div","stat");l.append(o("span","muted","Balance"),o("strong","mono",L(Number(t.balance)))),n.append(l)}t.error&&n.append(o("p","error-text",String(t.error)));let w=o("p","note");if(a)w.textContent="Spinning…";else if(t.phase==="result"){let l=Number(t.winnings??0);w.textContent=`${t.number} ${t.color} · ${l>0?`won ${L(l)}`:"no win"}`,w.className=l>0?"note win-text":"note"}else w.textContent=g>0?"Ready to spin.":"Place at least one chip.";n.append(w);let y=o("div","actions"),v=o("button","btn primary wide big",a?"SPINNING…":"SPIN WHEEL");v.disabled=a||g===0,v.addEventListener("click",()=>{Date.now()-He<600||(He=Date.now(),s("Spin"))});let E=o("button","btn wide","Clear bets");E.disabled=a||g===0,E.addEventListener("click",()=>s("ClearBets"));let S=o("button","btn ghost wide","Leave table");S.disabled=a,S.addEventListener("click",()=>{a||s("Leave")}),y.append(v,E,S),n.append(y)}var re=5,ae=3,F="CLOGDS",mt=["top","middle","bottom","V","inverted V"],K=0,Pe=0,A,X=null,G=null;function ft(){K=Date.now()+900}function We(){A!==void 0&&(window.clearInterval(A),A=void 0),X=null,G=null,K=0}function ht(e){return e.phase==="spinning"||Date.now()<K}function ie(e,n,t,s){X=t,G=e,A!==void 0&&(window.clearInterval(A),A=void 0);let a=ht(t);H(e),H(n);let r=o("h2",void 0,"Slots"),d=o("p","note","Match three or more symbols from the left. Five paylines.");e.append(r,d);let i=o("div","reels"),m=[],p=Array.isArray(t.reels)?t.reels.join(""):String(t.reels??"");for(let c=0;c<re;c+=1){let h=o("div","reel");for(let M=0;M<ae;M+=1){let N=c*ae+M,b=a?F[(N*3+Math.floor(Date.now()/70))%F.length]:p.length===re*ae?p[N]:p.length===re?p[c]:F[(N*5+c)%F.length],T=o("div","tile",b);T.classList.add(`sym-${b}`),a&&T.classList.add("spinning"),!a&&(t.win_cells??[]).includes(N)&&T.classList.add("win"),m.push(T),h.append(T)}i.append(h)}e.append(i);let u=o("div","win-lines");if(!a&&t.phase==="result"){let c=Number(t.winnings??0);if(c>0){let h=(t.win_lines??[]).map(M=>`${M} (${mt[M-1]??"line"})`).join(" · ");u.textContent=`${t.win_type==="mega"?"MEGA WIN":t.win_type==="big"?"BIG WIN":"WIN"} ${L(c)}${h?` · ${h}`:""}`,u.classList.add("win-text")}else u.textContent="No win — spin again."}else a?u.textContent="Spinning…":u.textContent="Pick a bet and spin.";if(e.append(u),a){let c=0;A=window.setInterval(()=>{c+=1;for(let h=0;h<m.length;h+=1){let M=F[(h*3+c)%F.length];m[h].textContent=M,m[h].className=`tile spinning sym-${M}`}Date.now()>=K&&X&&G&&(window.clearInterval(A),A=void 0,ie(G,n,X,s))},70)}n.append(o("div","label","Slots")),n.append(o("p","note","5 reels × 3 rows, 5 paylines."));let g=o("div","row"),f=o("button","btn small","−10"),w=o("strong","mono",L(Number(t.bet??0))),y=o("button","btn small","+10");f.disabled=a,y.disabled=a,f.addEventListener("click",()=>s({AdjustBet:-10})),y.addEventListener("click",()=>s({AdjustBet:10})),g.append(f,w,y),n.append(o("div","label","Bet"),g),t.error&&n.append(o("p","error-text",String(t.error)));let v=o("p","note");if(a)v.textContent="Spinning…";else if(t.phase==="result"){let c=Number(t.winnings??0);v.textContent=c>0?`Won ${L(c)}`:"No win this spin.",c>0&&v.classList.add("win-text")}else v.textContent="Ready when you are.";n.append(v);let E=o("div","actions"),S=o("button","btn primary wide big",a?"SPINNING…":"SPIN");S.disabled=a,S.addEventListener("click",()=>{Date.now()-Pe<900||(Pe=Date.now(),ft(),s("Spin"))});let l=o("button","btn ghost wide","Leave machine");l.disabled=a,l.addEventListener("click",()=>s("Leave")),E.append(S,l),n.append(E)}var bt=k("auth-screen"),gt=k("lobby-screen"),yt=k("game-screen"),kt=k("history-screen"),Ie=k("session-bar"),Lt=k("account-name"),xt=k("account-balance"),le=k("game-grid"),U=k("game-board"),Z=k("game-side"),wt=k("game-title"),ze=k("game-badge"),vt=k("history-body"),Tt=k("auth-form"),Ve=k("auth-user"),Q=k("auth-pass"),O=k("auth-error"),P=k("auth-submit"),_e=k("tab-login"),je=k("tab-register"),J=k("reconnect-overlay"),Fe=k("reconnect-message"),Oe=k("replaced-overlay"),x=null,D=null,q=null,_="login",ce=null,Ye=0,de=0,ue=new Map([["slots",10],["roulette",10],["blackjack",10],["crash",10]]),Xe={slots:"Slots",roulette:"Roulette",blackjack:"Blackjack",crash:"Crash"},Et={slots:"Five reels, five paylines. Match symbols from the left.",roulette:"European single-zero wheel with inside and outside bets.",blackjack:"Six-deck shoe. Blackjack pays 3:2.",crash:"A rising multiplier. Cash out before it crashes."},Mt=location.pathname.includes("/play/")?location.pathname.slice(0,location.pathname.indexOf("/play/")):"",Ct=`${location.protocol==="https:"?"wss":"ws"}://${location.host}${Mt}/ws`,ee=new j(Ct,{onHello(e){Ye=e.time-Date.now(),D=e.user??null,typeof e.balance=="number"&&(q=e.balance),ze.textContent=`v${e.version}`},onView(e){x=e,e.screen!=="slots"&&We(),e.screen!=="crash"&&Ce(),typeof e.balance=="number"&&(q=e.balance),e.screen==="lobby"&&"user"in e&&(D=e.user??null,D?Q.value="":q=null),de=0,J.hidden=!0,Ge(),D&&x.balance===null&&ce!==D&&(ce=D,ee.send("Info")),D||(ce=null)},onNotice(e){e.startsWith("Connected to:")||e.startsWith("Fingerprint:")||e.startsWith("User:")||e.startsWith("Account:")||e.startsWith("Balance:")||(te(e,be(e)),e.startsWith("Account created")&&(pe("login"),Q.value="",Ve.focus()),P.disabled=!1,P.textContent=_==="login"?"Log in":"Create account")},onError(e){te(e,"bad"),P.disabled=!1,P.textContent=_==="login"?"Log in":"Create account"},onReplaced(){Oe.hidden=!1},onPong(){},onOpen(){J.hidden=!0},onClosed(e){if(Oe.hidden!==!1){if(!e){Fe.textContent="The connection could not be restored. Reload the page to try again.",J.hidden=!1;return}de+=1,Fe.textContent=de>1?"Still trying to reach the game server…":"Trying to reach the game server…",J.hidden=!1}}});function B(e){ee.send(e)}function St(){return Date.now()+Ye}function pe(e){_=e,_e.classList.toggle("active",e==="login"),je.classList.toggle("active",e==="register"),P.textContent=e==="login"?"Log in":"Create account",Q.autocomplete=e==="login"?"current-password":"new-password",O.textContent=""}function V(e){bt.hidden=e!=="auth",gt.hidden=e!=="lobby",yt.hidden=e!=="game",kt.hidden=e!=="history"}function Nt(e){xt.textContent=L(e??null)}function Ge(){if(!x)return;let e=D;if(e?(Ie.hidden=!1,Lt.textContent=String(e)):Ie.hidden=!0,Nt(q),!e){V("auth"),O.textContent=x.login_error?String(x.login_error):"",x.screen==="lobby"&&x.connected===!1&&(O.textContent="The game server is not reachable yet. Retrying…");return}switch(x.screen){case"lobby":V("lobby"),Bt(x);break;case"history":V("history"),Ne(vt,x);break;case"slots":case"roulette":case"blackjack":case"crash":V("game"),wt.textContent=Xe[x.screen]??x.screen,ze.textContent=Ht(x),x.screen==="slots"?ie(U,Z,x,B):x.screen==="roulette"?De(U,Z,x,B):x.screen==="blackjack"?we(U,Z,x,B):Se(U,Z,x,B,{now:St});break;default:V("auth")}}function Ht(e){return e.screen==="crash"||e.screen==="blackjack"?String(e.status??""):e.screen==="slots"||e.screen==="roulette"?String(e.phase??""):""}function Bt(e){le.replaceChildren();let n=e.games??[];for(let[t,s]of n){let a=o("article",`game-card ${t}`);a.append(o("h3",void 0,s||Xe[t]||t)),a.append(o("p",void 0,Et[t]??"Play a round."));let r=o("div","row wrap"),d=o("label","field");d.style.flex="1",d.append(o("span","label","Bet"));let i=o("input","input bet-input");i.type="number",i.min="1",i.step="10",i.value=String(ue.get(t)??10),i.addEventListener("change",()=>{let p=Math.max(1,Math.floor(Number(i.value)||1));i.value=String(p),ue.set(t,p)}),d.append(i);let m=o("button","btn primary",t==="roulette"?"Open table":"Play");m.addEventListener("click",()=>{let p=Math.max(1,Math.floor(Number(i.value)||1));ue.set(t,p),B({Play:{game:t,bet:p}})}),r.append(d,m),a.append(r),le.append(a)}n.length===0&&le.append(o("p","muted","The server did not advertise any games."))}_e.addEventListener("click",()=>pe("login"));je.addEventListener("click",()=>pe("register"));Tt.addEventListener("submit",async e=>{e.preventDefault();let n=Ve.value.trim(),t=Q.value;if(!n||!t){O.textContent="Enter a username and a password.";return}P.disabled=!0,P.textContent="Working…",O.textContent="";try{let s=await ge(t);B(_==="login"?{Login:{user:n,credential:s}}:{Register:{user:n,credential:s}})}catch{O.textContent="This browser cannot hash the password (needs a secure context).",P.disabled=!1,P.textContent=_==="login"?"Log in":"Create account"}});k("logout").addEventListener("click",()=>B("Logout"));k("show-history").addEventListener("click",()=>B({History:{limit:50}}));k("refresh-lobby").addEventListener("click",()=>B("Info"));k("history-back").addEventListener("click",()=>B("ShowLobby"));k("leave-game").addEventListener("click",()=>{!x||(x.screen==="blackjack"&&x.status==="player"||x.screen==="crash"&&x.status==="running")&&!window.confirm("Leaving now forfeits the active bet. Continue?")||B("Leave")});k("reload-replaced").addEventListener("click",()=>location.reload());k("reload-reconnect").addEventListener("click",()=>location.reload());window.addEventListener("resize",()=>{window.clearTimeout(window.__resizeTimer),window.__resizeTimer=window.setTimeout(()=>{x&&Ge()},140)});window.setInterval(()=>{D&&ee.ping()},25e3);ee.connect();})();
diff --git a/web/play/src/main.ts b/web/play/src/main.ts
index f051429..2dd3ce9 100644
--- a/web/play/src/main.ts
+++ b/web/play/src/main.ts
@@ -68,6 +68,7 @@ const gateway = new Gateway(wsUrl, {
68 onHello(hello) { 68 onHello(hello) {
69 serverOffset = hello.time - Date.now(); 69 serverOffset = hello.time - Date.now();
70 currentUser = hello.user ?? null; 70 currentUser = hello.user ?? null;
71 if (typeof hello.balance === "number") lastBalance = hello.balance;
71 gameBadge.textContent = `v${hello.version}`; 72 gameBadge.textContent = `v${hello.version}`;
72 }, 73 },
73 onView(next) { 74 onView(next) {
diff --git a/web/play/src/types.ts b/web/play/src/types.ts
index e594a0b..ae2c4c7 100644
--- a/web/play/src/types.ts
+++ b/web/play/src/types.ts
@@ -9,6 +9,8 @@ export type Hello = {
9 grace_seconds: number; 9 grace_seconds: number;
10 /** Signed-in user restored after a refresh, if any. */ 10 /** Signed-in user restored after a refresh, if any. */
11 user?: string | null; 11 user?: string | null;
12 /** Last known balance, for views that do not carry one. */
13 balance?: number | null;
12 /** Estimated game-server Unix time in milliseconds. */ 14 /** Estimated game-server Unix time in milliseconds. */
13 time: number; 15 time: number;
14}; 16};