Liveloop

An interactive timeline for social media. Every post is a tiny app you can play, save, and remix.

Product

  • Feed
  • Create
  • Claude Code plugin
  • Blog

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA

Project

  • Templates
© 2026 Liveloop. All rights reserved.
LiveloopVersion history

v1Current

@liveloop · 5/18/2026, 12:51:47 PM

Initial version — all lines are new.

+<style>
+ html,body{height:100%;margin:0;background:#070612;overflow:hidden;}
+ *{box-sizing:border-box;}
+ #wrap{position:relative;height:100%;width:100%;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
+ canvas{display:block;width:100%;height:100%;touch-action:none;}
+ #hud{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:flex-start;padding:.7rem 1.4rem;color:#e9d5ff;font-size:.95rem;font-weight:800;letter-spacing:.04em;pointer-events:none;text-shadow:0 0 10px rgba(0,0,0,.95);}
+ #hud .lab{font-size:.56rem;opacity:.55;letter-spacing:.14em;margin-bottom:.05rem;}
+ #hud .col{text-align:center;}
+ #hud .won{color:#22d3ee;}
+ #hud .lost{color:#fb7185;}
+ #ov{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;text-align:center;padding:1.6rem;background:rgba(7,6,18,.86);color:#e9d5ff;cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:none;user-select:none;-webkit-user-select:none;}
+ #ov h1{margin:0;font-size:2.05rem;font-weight:900;letter-spacing:.02em;background:linear-gradient(90deg,#22d3ee,#a855f7,#fb7185);-webkit-background-clip:text;background-clip:text;color:transparent;}
+ #ov .sub{margin:.1rem 0 0;font-size:.9rem;color:#a5b4fc;max-width:26ch;}
+ #ov .pulse{margin-top:.5rem;font-size:.84rem;color:#67e8f9;animation:tlpulse 1.3s ease-in-out infinite;}
+ @keyframes tlpulse{0%,100%{opacity:.35}50%{opacity:1}}
+</style>
+<div id="wrap">
+ <canvas id="cv"></canvas>
+ <div id="hud">
+ <div class="col"><div class="lab">WON</div><div id="won" class="won">0</div></div>
+ <div class="col"><div class="lab">DREW</div><div id="drew">0</div></div>
+ <div class="col"><div class="lab">LOST</div><div id="lost" class="lost">0</div></div>
+ </div>
+ <div id="ov">
+ <h1>TRIPLE LINE</h1>
+ <p class="sub">You are X and move first. Get three in a row before the computer.</p>
+ <p class="pulse">Tap to play</p>
+ </div>
+</div>
+<script>
+(function(){
+ var LL=window.liveloop||null;
+ var cv=document.getElementById('cv'), ctx=cv.getContext('2d');
+ var ov=document.getElementById('ov');
+ var wonEl=document.getElementById('won'), drewEl=document.getElementById('drew'),
+ lostEl=document.getElementById('lost');
+ var LINES=[[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
+ var W=0,H=0,dpr=1,cell=0,bx=0,by=0;
+ var state='start', board=[], turn='X', result=null, winLine=null;
+ var winAnim=0, aiTimer=0, placeT=[];
+ var stats={w:0,d:0,l:0};
+ var lastT=0,raf=0,running=false;
+
+ function setCanvas(){
+ var rc=cv.getBoundingClientRect();
+ W=Math.max(200,rc.width); H=Math.max(280,rc.height);
+ dpr=Math.min(window.devicePixelRatio||1,2.5);
+ cv.width=Math.round(W*dpr); cv.height=Math.round(H*dpr);
+ ctx.setTransform(dpr,0,0,dpr,0,0);
+ cell=Math.floor(Math.min(W*0.82,H*0.46)/3);
+ bx=Math.round((W-cell*3)/2);
+ by=Math.round(H*0.2);
+ }
+ function newRound(){
+ board=['','','','','','','','',''];
+ placeT=[0,0,0,0,0,0,0,0,0];
+ turn='X'; result=null; winLine=null; winAnim=0; aiTimer=0;
+ state='playing';
+ }
+ function syncHud(){
+ wonEl.textContent=stats.w; drewEl.textContent=stats.d; lostEl.textContent=stats.l;
+ }
+ function winnerOf(b){
+ for(var i=0;i<LINES.length;i++){
+ var L=LINES[i];
+ if(b[L[0]] && b[L[0]]===b[L[1]] && b[L[0]]===b[L[2]]) return {who:b[L[0]],line:L};
+ }
+ var full=true;
+ for(i=0;i<9;i++) if(!b[i]) full=false;
+ if(full) return {who:'draw',line:null};
+ return null;
+ }
+ function endRound(w){
+ result=w.who; winLine=w.line; state='over'; winAnim=0;
+ if(w.who==='X') stats.w++;
+ else if(w.who==='O') stats.l++;
+ else stats.d++;
+ syncHud(); saveStats();
+ }
+ function evaluate(){
+ var w=winnerOf(board);
+ if(w){ endRound(w); return; }
+ turn=(turn==='X')?'O':'X';
+ if(turn==='O') aiTimer=26;
+ }
+ function aiPick(){
+ var i,b;
+ // win if possible
+ for(i=0;i<9;i++) if(!board[i]){
+ b=board.slice(); b[i]='O';
+ if(winnerOf(b)&&winnerOf(b).who==='O') return i;
+ }
+ // block the player
+ for(i=0;i<9;i++) if(!board[i]){
+ b=board.slice(); b[i]='X';
+ if(winnerOf(b)&&winnerOf(b).who==='X') return i;
+ }
+ if(!board[4]) return 4;
+ var corners=[0,2,6,8], sides=[1,3,5,7], pool=[];
+ for(i=0;i<4;i++) if(!board[corners[i]]) pool.push(corners[i]);
+ if(pool.length) return pool[Math.floor(Math.random()*pool.length)];
+ for(i=0;i<4;i++) if(!board[sides[i]]) pool.push(sides[i]);
+ return pool[Math.floor(Math.random()*pool.length)];
+ }
+ function place(i,who){
+ board[i]=who; placeT[i]=performance.now();
+ }
+ function step(dt){
+ if(state==='playing' && turn==='O' && aiTimer>0){
+ aiTimer-=dt;
+ if(aiTimer<=0){
+ var mv=aiPick();
+ if(mv!=null){ place(mv,'O'); evaluate(); }
+ }
+ }
+ if(state==='over' && winAnim<1){
+ winAnim+=dt*0.07;
+ if(winAnim>1) winAnim=1;
+ }
+ }
+ function ccx(i){ return bx+(i%3)*cell+cell/2; }
+ function ccy(i){ return by+Math.floor(i/3)*cell+cell/2; }
+ function markScale(i){
+ if(!placeT[i]) return 1;
+ var t=(performance.now()-placeT[i])/150;
+ if(t>1) t=1;
+ return 1-(1-t)*(1-t);
+ }
+ function drawX(i,sc){
+ var x=ccx(i),y=ccy(i),r=cell*0.27*sc;
+ ctx.strokeStyle='#22d3ee'; ctx.lineWidth=cell*0.1; ctx.lineCap='round';
+ ctx.shadowColor='#22d3ee'; ctx.shadowBlur=12;
+ ctx.beginPath();
+ ctx.moveTo(x-r,y-r); ctx.lineTo(x+r,y+r);
+ ctx.moveTo(x+r,y-r); ctx.lineTo(x-r,y+r);
+ ctx.stroke();
+ ctx.shadowBlur=0;
+ }
+ function drawO(i,sc){
+ var x=ccx(i),y=ccy(i),r=cell*0.27*sc;
+ ctx.strokeStyle='#fb7185'; ctx.lineWidth=cell*0.1;
+ ctx.shadowColor='#fb7185'; ctx.shadowBlur=12;
+ ctx.beginPath(); ctx.arc(x,y,r,0,6.2832); ctx.stroke();
+ ctx.shadowBlur=0;
+ }
+ function render(){
+ var bg=ctx.createLinearGradient(0,0,0,H);
+ bg.addColorStop(0,'#140d30'); bg.addColorStop(1,'#070612');
+ ctx.fillStyle=bg; ctx.fillRect(0,0,W,H);
+ var i;
+ for(i=0;i<9;i++){
+ var x=bx+(i%3)*cell, y=by+Math.floor(i/3)*cell;
+ ctx.fillStyle='rgba(255,255,255,0.05)';
+ ctx.beginPath();
+ var rad=cell*0.16,pad=cell*0.05;
+ ctx.moveTo(x+pad+rad,y+pad);
+ ctx.arcTo(x+cell-pad,y+pad,x+cell-pad,y+cell-pad,rad);
+ ctx.arcTo(x+cell-pad,y+cell-pad,x+pad,y+cell-pad,rad);
+ ctx.arcTo(x+pad,y+cell-pad,x+pad,y+pad,rad);
+ ctx.arcTo(x+pad,y+pad,x+cell-pad,y+pad,rad);
+ ctx.closePath(); ctx.fill();
+ }
+ for(i=0;i<9;i++){
+ if(board[i]==='X') drawX(i,markScale(i));
+ else if(board[i]==='O') drawO(i,markScale(i));
+ }
+ if(winLine){
+ var a=winLine[0], b=winLine[2];
+ var x1=ccx(a),y1=ccy(a),x2=ccx(b),y2=ccy(b);
+ var ex=x1+(x2-x1)*winAnim, ey=y1+(y2-y1)*winAnim;
+ ctx.strokeStyle='#fde68a'; ctx.lineWidth=cell*0.07; ctx.lineCap='round';
+ ctx.shadowColor='#fde68a'; ctx.shadowBlur=16;
+ ctx.beginPath(); ctx.moveTo(x1,y1); ctx.lineTo(ex,ey); ctx.stroke();
+ ctx.shadowBlur=0;
+ }
+ // status text
+ ctx.textAlign='center';
+ var ty=by+cell*3+H*0.07;
+ if(state==='playing'){
+ ctx.fillStyle=turn==='X'?'#67e8f9':'#fb7185';
+ ctx.font='800 '+Math.round(H*0.03)+'px system-ui';
+ ctx.fillText(turn==='X'?'Your turn':'Computer thinking...',W/2,ty);
+ } else if(state==='over'){
+ ctx.fillStyle=result==='X'?'#22d3ee':(result==='O'?'#fb7185':'#a5b4fc');
+ ctx.font='900 '+Math.round(H*0.05)+'px system-ui';
+ ctx.fillText(result==='X'?'YOU WIN':(result==='O'?'COMPUTER WINS':'DRAW'),W/2,ty);
+ ctx.fillStyle='#a5b4fc';
+ ctx.font='700 '+Math.round(H*0.026)+'px system-ui';
+ ctx.fillText('Tap for the next round',W/2,ty+H*0.05);
+ }
+ }
+ function frame(now){
+ if(!running) return;
+ var dt=(now-lastT)/16.667; lastT=now;
+ if(dt<0.2)dt=0.2; if(dt>2.6)dt=2.6;
+ step(dt); render();
+ raf=requestAnimationFrame(frame);
+ }
+ function startLoop(){
+ if(running) return;
+ running=true; lastT=performance.now(); raf=requestAnimationFrame(frame);
+ }
+ function stopLoop(){ running=false; if(raf)cancelAnimationFrame(raf); raf=0; }
+ function saveStats(){
+ if(LL&&LL.storage&&LL.storage.set){ try{ LL.storage.set({stats:stats}); }catch(e){} }
+ }
+ function loadStats(){
+ if(LL&&LL.storage&&LL.storage.get){
+ LL.storage.get().then(function(s){
+ if(s&&s.stats&&typeof s.stats.w==='number'){ stats=s.stats; syncHud(); }
+ },function(){});
+ }
+ }
+ cv.addEventListener('pointerdown',function(e){
+ if(state==='over'){ newRound(); return; }
+ if(state!=='playing'||turn!=='X') return;
+ var rc=cv.getBoundingClientRect();
+ var px=e.clientX-rc.left, py=e.clientY-rc.top;
+ var c=Math.floor((px-bx)/cell), r=Math.floor((py-by)/cell);
+ if(c<0||c>2||r<0||r>2) return;
+ var idx=r*3+c;
+ if(board[idx]) return;
+ place(idx,'X'); evaluate();
+ });
+ ov.addEventListener('pointerdown',function(e){
+ e.preventDefault();
+ ov.style.display='none';
+ newRound();
+ });
+ function onResize(){ setCanvas(); }
+ window.addEventListener('resize',onResize);
+ if(LL && LL.onResize) LL.onResize(onResize);
+ if(LL && LL.onVisibility) LL.onVisibility(function(v){ if(v) startLoop(); else stopLoop(); });
+ document.addEventListener('visibilitychange',function(){
+ if(document.hidden) stopLoop(); else startLoop();
+ });
+
+ setCanvas(); syncHud(); loadStats(); startLoop();
+})();
+</script>

v1Current

@liveloop · 5/18/2026, 12:51:47 PM

Initial version — all lines are new.

+<style>
+ html,body{height:100%;margin:0;background:#070612;overflow:hidden;}
+ *{box-sizing:border-box;}
+ #wrap{position:relative;height:100%;width:100%;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
+ canvas{display:block;width:100%;height:100%;touch-action:none;}
+ #hud{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:flex-start;padding:.7rem 1.4rem;color:#e9d5ff;font-size:.95rem;font-weight:800;letter-spacing:.04em;pointer-events:none;text-shadow:0 0 10px rgba(0,0,0,.95);}
+ #hud .lab{font-size:.56rem;opacity:.55;letter-spacing:.14em;margin-bottom:.05rem;}
+ #hud .col{text-align:center;}
+ #hud .won{color:#22d3ee;}
+ #hud .lost{color:#fb7185;}
+ #ov{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;text-align:center;padding:1.6rem;background:rgba(7,6,18,.86);color:#e9d5ff;cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:none;user-select:none;-webkit-user-select:none;}
+ #ov h1{margin:0;font-size:2.05rem;font-weight:900;letter-spacing:.02em;background:linear-gradient(90deg,#22d3ee,#a855f7,#fb7185);-webkit-background-clip:text;background-clip:text;color:transparent;}
+ #ov .sub{margin:.1rem 0 0;font-size:.9rem;color:#a5b4fc;max-width:26ch;}
+ #ov .pulse{margin-top:.5rem;font-size:.84rem;color:#67e8f9;animation:tlpulse 1.3s ease-in-out infinite;}
+ @keyframes tlpulse{0%,100%{opacity:.35}50%{opacity:1}}
+</style>
+<div id="wrap">
+ <canvas id="cv"></canvas>
+ <div id="hud">
+ <div class="col"><div class="lab">WON</div><div id="won" class="won">0</div></div>
+ <div class="col"><div class="lab">DREW</div><div id="drew">0</div></div>
+ <div class="col"><div class="lab">LOST</div><div id="lost" class="lost">0</div></div>
+ </div>
+ <div id="ov">
+ <h1>TRIPLE LINE</h1>
+ <p class="sub">You are X and move first. Get three in a row before the computer.</p>
+ <p class="pulse">Tap to play</p>
+ </div>
+</div>
+<script>
+(function(){
+ var LL=window.liveloop||null;
+ var cv=document.getElementById('cv'), ctx=cv.getContext('2d');
+ var ov=document.getElementById('ov');
+ var wonEl=document.getElementById('won'), drewEl=document.getElementById('drew'),
+ lostEl=document.getElementById('lost');
+ var LINES=[[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
+ var W=0,H=0,dpr=1,cell=0,bx=0,by=0;
+ var state='start', board=[], turn='X', result=null, winLine=null;
+ var winAnim=0, aiTimer=0, placeT=[];
+ var stats={w:0,d:0,l:0};
+ var lastT=0,raf=0,running=false;
+
+ function setCanvas(){
+ var rc=cv.getBoundingClientRect();
+ W=Math.max(200,rc.width); H=Math.max(280,rc.height);
+ dpr=Math.min(window.devicePixelRatio||1,2.5);
+ cv.width=Math.round(W*dpr); cv.height=Math.round(H*dpr);
+ ctx.setTransform(dpr,0,0,dpr,0,0);
+ cell=Math.floor(Math.min(W*0.82,H*0.46)/3);
+ bx=Math.round((W-cell*3)/2);
+ by=Math.round(H*0.2);
+ }
+ function newRound(){
+ board=['','','','','','','','',''];
+ placeT=[0,0,0,0,0,0,0,0,0];
+ turn='X'; result=null; winLine=null; winAnim=0; aiTimer=0;
+ state='playing';
+ }
+ function syncHud(){
+ wonEl.textContent=stats.w; drewEl.textContent=stats.d; lostEl.textContent=stats.l;
+ }
+ function winnerOf(b){
+ for(var i=0;i<LINES.length;i++){
+ var L=LINES[i];
+ if(b[L[0]] && b[L[0]]===b[L[1]] && b[L[0]]===b[L[2]]) return {who:b[L[0]],line:L};
+ }
+ var full=true;
+ for(i=0;i<9;i++) if(!b[i]) full=false;
+ if(full) return {who:'draw',line:null};
+ return null;
+ }
+ function endRound(w){
+ result=w.who; winLine=w.line; state='over'; winAnim=0;
+ if(w.who==='X') stats.w++;
+ else if(w.who==='O') stats.l++;
+ else stats.d++;
+ syncHud(); saveStats();
+ }
+ function evaluate(){
+ var w=winnerOf(board);
+ if(w){ endRound(w); return; }
+ turn=(turn==='X')?'O':'X';
+ if(turn==='O') aiTimer=26;
+ }
+ function aiPick(){
+ var i,b;
+ // win if possible
+ for(i=0;i<9;i++) if(!board[i]){
+ b=board.slice(); b[i]='O';
+ if(winnerOf(b)&&winnerOf(b).who==='O') return i;
+ }
+ // block the player
+ for(i=0;i<9;i++) if(!board[i]){
+ b=board.slice(); b[i]='X';
+ if(winnerOf(b)&&winnerOf(b).who==='X') return i;
+ }
+ if(!board[4]) return 4;
+ var corners=[0,2,6,8], sides=[1,3,5,7], pool=[];
+ for(i=0;i<4;i++) if(!board[corners[i]]) pool.push(corners[i]);
+ if(pool.length) return pool[Math.floor(Math.random()*pool.length)];
+ for(i=0;i<4;i++) if(!board[sides[i]]) pool.push(sides[i]);
+ return pool[Math.floor(Math.random()*pool.length)];
+ }
+ function place(i,who){
+ board[i]=who; placeT[i]=performance.now();
+ }
+ function step(dt){
+ if(state==='playing' && turn==='O' && aiTimer>0){
+ aiTimer-=dt;
+ if(aiTimer<=0){
+ var mv=aiPick();
+ if(mv!=null){ place(mv,'O'); evaluate(); }
+ }
+ }
+ if(state==='over' && winAnim<1){
+ winAnim+=dt*0.07;
+ if(winAnim>1) winAnim=1;
+ }
+ }
+ function ccx(i){ return bx+(i%3)*cell+cell/2; }
+ function ccy(i){ return by+Math.floor(i/3)*cell+cell/2; }
+ function markScale(i){
+ if(!placeT[i]) return 1;
+ var t=(performance.now()-placeT[i])/150;
+ if(t>1) t=1;
+ return 1-(1-t)*(1-t);
+ }
+ function drawX(i,sc){
+ var x=ccx(i),y=ccy(i),r=cell*0.27*sc;
+ ctx.strokeStyle='#22d3ee'; ctx.lineWidth=cell*0.1; ctx.lineCap='round';
+ ctx.shadowColor='#22d3ee'; ctx.shadowBlur=12;
+ ctx.beginPath();
+ ctx.moveTo(x-r,y-r); ctx.lineTo(x+r,y+r);
+ ctx.moveTo(x+r,y-r); ctx.lineTo(x-r,y+r);
+ ctx.stroke();
+ ctx.shadowBlur=0;
+ }
+ function drawO(i,sc){
+ var x=ccx(i),y=ccy(i),r=cell*0.27*sc;
+ ctx.strokeStyle='#fb7185'; ctx.lineWidth=cell*0.1;
+ ctx.shadowColor='#fb7185'; ctx.shadowBlur=12;
+ ctx.beginPath(); ctx.arc(x,y,r,0,6.2832); ctx.stroke();
+ ctx.shadowBlur=0;
+ }
+ function render(){
+ var bg=ctx.createLinearGradient(0,0,0,H);
+ bg.addColorStop(0,'#140d30'); bg.addColorStop(1,'#070612');
+ ctx.fillStyle=bg; ctx.fillRect(0,0,W,H);
+ var i;
+ for(i=0;i<9;i++){
+ var x=bx+(i%3)*cell, y=by+Math.floor(i/3)*cell;
+ ctx.fillStyle='rgba(255,255,255,0.05)';
+ ctx.beginPath();
+ var rad=cell*0.16,pad=cell*0.05;
+ ctx.moveTo(x+pad+rad,y+pad);
+ ctx.arcTo(x+cell-pad,y+pad,x+cell-pad,y+cell-pad,rad);
+ ctx.arcTo(x+cell-pad,y+cell-pad,x+pad,y+cell-pad,rad);
+ ctx.arcTo(x+pad,y+cell-pad,x+pad,y+pad,rad);
+ ctx.arcTo(x+pad,y+pad,x+cell-pad,y+pad,rad);
+ ctx.closePath(); ctx.fill();
+ }
+ for(i=0;i<9;i++){
+ if(board[i]==='X') drawX(i,markScale(i));
+ else if(board[i]==='O') drawO(i,markScale(i));
+ }
+ if(winLine){
+ var a=winLine[0], b=winLine[2];
+ var x1=ccx(a),y1=ccy(a),x2=ccx(b),y2=ccy(b);
+ var ex=x1+(x2-x1)*winAnim, ey=y1+(y2-y1)*winAnim;
+ ctx.strokeStyle='#fde68a'; ctx.lineWidth=cell*0.07; ctx.lineCap='round';
+ ctx.shadowColor='#fde68a'; ctx.shadowBlur=16;
+ ctx.beginPath(); ctx.moveTo(x1,y1); ctx.lineTo(ex,ey); ctx.stroke();
+ ctx.shadowBlur=0;
+ }
+ // status text
+ ctx.textAlign='center';
+ var ty=by+cell*3+H*0.07;
+ if(state==='playing'){
+ ctx.fillStyle=turn==='X'?'#67e8f9':'#fb7185';
+ ctx.font='800 '+Math.round(H*0.03)+'px system-ui';
+ ctx.fillText(turn==='X'?'Your turn':'Computer thinking...',W/2,ty);
+ } else if(state==='over'){
+ ctx.fillStyle=result==='X'?'#22d3ee':(result==='O'?'#fb7185':'#a5b4fc');
+ ctx.font='900 '+Math.round(H*0.05)+'px system-ui';
+ ctx.fillText(result==='X'?'YOU WIN':(result==='O'?'COMPUTER WINS':'DRAW'),W/2,ty);
+ ctx.fillStyle='#a5b4fc';
+ ctx.font='700 '+Math.round(H*0.026)+'px system-ui';
+ ctx.fillText('Tap for the next round',W/2,ty+H*0.05);
+ }
+ }
+ function frame(now){
+ if(!running) return;
+ var dt=(now-lastT)/16.667; lastT=now;
+ if(dt<0.2)dt=0.2; if(dt>2.6)dt=2.6;
+ step(dt); render();
+ raf=requestAnimationFrame(frame);
+ }
+ function startLoop(){
+ if(running) return;
+ running=true; lastT=performance.now(); raf=requestAnimationFrame(frame);
+ }
+ function stopLoop(){ running=false; if(raf)cancelAnimationFrame(raf); raf=0; }
+ function saveStats(){
+ if(LL&&LL.storage&&LL.storage.set){ try{ LL.storage.set({stats:stats}); }catch(e){} }
+ }
+ function loadStats(){
+ if(LL&&LL.storage&&LL.storage.get){
+ LL.storage.get().then(function(s){
+ if(s&&s.stats&&typeof s.stats.w==='number'){ stats=s.stats; syncHud(); }
+ },function(){});
+ }
+ }
+ cv.addEventListener('pointerdown',function(e){
+ if(state==='over'){ newRound(); return; }
+ if(state!=='playing'||turn!=='X') return;
+ var rc=cv.getBoundingClientRect();
+ var px=e.clientX-rc.left, py=e.clientY-rc.top;
+ var c=Math.floor((px-bx)/cell), r=Math.floor((py-by)/cell);
+ if(c<0||c>2||r<0||r>2) return;
+ var idx=r*3+c;
+ if(board[idx]) return;
+ place(idx,'X'); evaluate();
+ });
+ ov.addEventListener('pointerdown',function(e){
+ e.preventDefault();
+ ov.style.display='none';
+ newRound();
+ });
+ function onResize(){ setCanvas(); }
+ window.addEventListener('resize',onResize);
+ if(LL && LL.onResize) LL.onResize(onResize);
+ if(LL && LL.onVisibility) LL.onVisibility(function(v){ if(v) startLoop(); else stopLoop(); });
+ document.addEventListener('visibilitychange',function(){
+ if(document.hidden) stopLoop(); else startLoop();
+ });
+
+ setCanvas(); syncHud(); loadStats(); startLoop();
+})();
+</script>
← Version history