← back to Model Arena
data/artifacts/04692f72ef1d/claude-code.html
372 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tetris</title>
<style>
:root{
--bg:#0d1117; --panel:#161b22; --grid:#1c2330; --line:#2a3345;
--text:#e6edf3; --muted:#8b949e; --accent:#58a6ff;
}
*{box-sizing:border-box; margin:0; padding:0;}
html,body{height:100%;}
body{
background:radial-gradient(circle at 50% -10%, #1a2233, var(--bg));
color:var(--text);
font-family:"Segoe UI",system-ui,-apple-system,sans-serif;
display:flex; align-items:center; justify-content:center;
min-height:100vh; padding:16px; user-select:none;
}
.wrap{
display:flex; gap:20px; align-items:flex-start;
flex-wrap:wrap; justify-content:center;
}
.board-shell{position:relative;}
canvas{
background:var(--grid);
border:2px solid var(--line);
border-radius:8px;
display:block;
box-shadow:0 10px 40px rgba(0,0,0,.5);
}
.side{
display:flex; flex-direction:column; gap:16px; width:180px;
}
.card{
background:var(--panel); border:1px solid var(--line);
border-radius:10px; padding:14px;
}
.card h2{
font-size:11px; letter-spacing:2px; text-transform:uppercase;
color:var(--muted); margin-bottom:8px; font-weight:600;
}
.stat{font-size:28px; font-weight:700; font-variant-numeric:tabular-nums;}
#next{display:block; margin:0 auto; background:var(--grid); border-radius:6px;}
.keys{font-size:12px; line-height:1.9; color:var(--muted);}
.keys b{color:var(--text); font-weight:600;}
kbd{
background:#21262d; border:1px solid #30363d; border-bottom-width:2px;
border-radius:4px; padding:1px 6px; font-size:11px; color:var(--text);
}
.title{
font-size:26px; font-weight:800; letter-spacing:4px;
background:linear-gradient(90deg,#58a6ff,#bc8cff,#ff7b72);
-webkit-background-clip:text; background-clip:text; color:transparent;
text-align:center; margin-bottom:4px;
}
.overlay{
position:absolute; inset:0; display:flex; flex-direction:column;
align-items:center; justify-content:center; gap:14px;
background:rgba(6,9,15,.82); border-radius:8px; backdrop-filter:blur(3px);
text-align:center; padding:20px;
}
.overlay.hidden{display:none;}
.overlay h3{font-size:30px; font-weight:800; letter-spacing:1px;}
.overlay p{color:var(--muted); font-size:14px; max-width:220px;}
button{
background:var(--accent); color:#04101f; border:none;
padding:11px 22px; font-size:15px; font-weight:700; border-radius:8px;
cursor:pointer; letter-spacing:.5px; transition:transform .08s, filter .15s;
}
button:hover{filter:brightness(1.1);}
button:active{transform:scale(.96);}
</style>
</head>
<body>
<div class="wrap">
<div class="board-shell">
<canvas id="board" width="300" height="600"></canvas>
<div id="overlay" class="overlay">
<h3 id="ovTitle">TETRIS</h3>
<p id="ovText">Stack the blocks, clear the lines.</p>
<button id="startBtn">Start Game</button>
</div>
</div>
<div class="side">
<div class="title">TETRIS</div>
<div class="card">
<h2>Score</h2>
<div class="stat" id="score">0</div>
</div>
<div class="card">
<h2>Next</h2>
<canvas id="next" width="120" height="120"></canvas>
</div>
<div class="card" style="display:flex; gap:12px;">
<div><h2>Lines</h2><div class="stat" id="lines" style="font-size:20px;">0</div></div>
<div><h2>Level</h2><div class="stat" id="level" style="font-size:20px;">1</div></div>
</div>
<div class="card keys">
<h2>Controls</h2>
<div><kbd>←</kbd> <kbd>→</kbd> <b>Move</b></div>
<div><kbd>↑</kbd> <b>Rotate</b></div>
<div><kbd>↓</kbd> <b>Soft drop</b></div>
<div><kbd>Space</kbd> <b>Hard drop</b></div>
<div><kbd>P</kbd> <b>Pause</b></div>
</div>
</div>
</div>
<script>
(function(){
"use strict";
const COLS = 10, ROWS = 20, CELL = 30;
const board = document.getElementById("board");
const ctx = board.getContext("2d");
const nextCv = document.getElementById("next");
const nctx = nextCv.getContext("2d");
const scoreEl = document.getElementById("score");
const linesEl = document.getElementById("lines");
const levelEl = document.getElementById("level");
const overlay = document.getElementById("overlay");
const ovTitle = document.getElementById("ovTitle");
const ovText = document.getElementById("ovText");
const startBtn= document.getElementById("startBtn");
// Tetromino shapes (rotation states derived by matrix rotation from base)
const SHAPES = {
I: {color:"#4ad9d9", cells:[[0,1],[1,1],[2,1],[3,1]], size:4},
O: {color:"#f2d94e", cells:[[1,0],[2,0],[1,1],[2,1]], size:4},
T: {color:"#b06cf0", cells:[[1,0],[0,1],[1,1],[2,1]], size:3},
S: {color:"#5ad863", cells:[[1,0],[2,0],[0,1],[1,1]], size:3},
Z: {color:"#f2604e", cells:[[0,0],[1,0],[1,1],[2,1]], size:3},
J: {color:"#5a86f0", cells:[[0,0],[0,1],[1,1],[2,1]], size:3},
L: {color:"#f0a03c", cells:[[2,0],[0,1],[1,1],[2,1]], size:3}
};
const TYPES = Object.keys(SHAPES);
let grid, cur, nextType, score, lines, level, dropInterval, dropTimer;
let running=false, paused=false, over=false;
function emptyGrid(){
return Array.from({length:ROWS}, ()=>Array(COLS).fill(null));
}
function makePiece(type){
const s = SHAPES[type];
// build a size x size matrix
const m = Array.from({length:s.size},()=>Array(s.size).fill(0));
s.cells.forEach(([x,y])=> m[y][x]=1);
return {
type, color:s.color, matrix:m,
x: Math.floor((COLS - s.size)/2), y:-topOffset(m)
};
}
function topOffset(m){
for(let y=0;y<m.length;y++) if(m[y].some(v=>v)) return y;
return 0;
}
function rotate(matrix){
const n = matrix.length;
const r = Array.from({length:n},()=>Array(n).fill(0));
for(let y=0;y<n;y++) for(let x=0;x<n;x++) r[x][n-1-y]=matrix[y][x];
return r;
}
function collide(piece, offX, offY, matrix){
const m = matrix || piece.matrix;
for(let y=0;y<m.length;y++){
for(let x=0;x<m[y].length;x++){
if(!m[y][x]) continue;
const nx = piece.x + x + offX;
const ny = piece.y + y + offY;
if(nx<0 || nx>=COLS || ny>=ROWS) return true;
if(ny>=0 && grid[ny][nx]) return true;
}
}
return false;
}
function merge(piece){
piece.matrix.forEach((row,y)=>row.forEach((v,x)=>{
if(v){
const ny = piece.y+y, nx = piece.x+x;
if(ny>=0) grid[ny][nx]=piece.color;
}
}));
}
function clearLines(){
let cleared=0;
for(let y=ROWS-1;y>=0;y--){
if(grid[y].every(c=>c)){
grid.splice(y,1);
grid.unshift(Array(COLS).fill(null));
cleared++; y++;
}
}
if(cleared){
const pts=[0,100,300,500,800];
score += pts[cleared]*level;
lines += cleared;
const newLevel = Math.floor(lines/10)+1;
if(newLevel!==level){ level=newLevel; dropInterval=Math.max(80, 1000-(level-1)*80); }
updateStats();
}
}
function spawn(){
cur = makePiece(nextType);
nextType = TYPES[Math.floor(Math.random()*TYPES.length)];
drawNext();
if(collide(cur,0,0)){ gameOver(); }
}
function drop(){
if(!collide(cur,0,1)){
cur.y++;
} else {
merge(cur);
clearLines();
spawn();
}
draw();
}
function hardDrop(){
let d=0;
while(!collide(cur,0,d+1)) d++;
cur.y+=d;
score += d*2;
merge(cur); clearLines(); spawn(); updateStats(); draw();
}
function move(dir){
if(!collide(cur,dir,0)) cur.x+=dir, draw();
}
function tryRotate(){
const r = rotate(cur.matrix);
// wall kicks
for(const k of [0,-1,1,-2,2]){
if(!collide(cur,k,0,r)){ cur.matrix=r; cur.x+=k; draw(); return; }
}
}
// ---------- rendering ----------
function cellRect(cx,x,y,color,ctx2){
ctx2.fillStyle=color;
ctx2.fillRect(x*cx, y*cx, cx, cx);
ctx2.fillStyle="rgba(255,255,255,.18)";
ctx2.fillRect(x*cx, y*cx, cx, cx*0.18);
ctx2.fillStyle="rgba(0,0,0,.22)";
ctx2.fillRect(x*cx, y*cx+cx*0.82, cx, cx*0.18);
ctx2.strokeStyle="rgba(0,0,0,.35)";
ctx2.lineWidth=1;
ctx2.strokeRect(x*cx+0.5, y*cx+0.5, cx-1, cx-1);
}
function draw(){
ctx.clearRect(0,0,board.width,board.height);
// grid lines
ctx.strokeStyle="rgba(255,255,255,.04)";
ctx.lineWidth=1;
for(let x=1;x<COLS;x++){ ctx.beginPath(); ctx.moveTo(x*CELL,0); ctx.lineTo(x*CELL,ROWS*CELL); ctx.stroke(); }
for(let y=1;y<ROWS;y++){ ctx.beginPath(); ctx.moveTo(0,y*CELL); ctx.lineTo(COLS*CELL,y*CELL); ctx.stroke(); }
// settled
for(let y=0;y<ROWS;y++) for(let x=0;x<COLS;x++)
if(grid[y][x]) cellRect(CELL,x,y,grid[y][x],ctx);
if(!cur) return;
// ghost
let gd=0; while(!collide(cur,0,gd+1)) gd++;
ctx.globalAlpha=0.22;
cur.matrix.forEach((row,y)=>row.forEach((v,x)=>{
if(v && cur.y+y+gd>=0) cellRect(CELL,cur.x+x,cur.y+y+gd,cur.color,ctx);
}));
ctx.globalAlpha=1;
// current
cur.matrix.forEach((row,y)=>row.forEach((v,x)=>{
if(v && cur.y+y>=0) cellRect(CELL,cur.x+x,cur.y+y,cur.color,ctx);
}));
}
function drawNext(){
nctx.clearRect(0,0,nextCv.width,nextCv.height);
const s = SHAPES[nextType];
const cx = 24;
// center it
let minx=99,maxx=-9,miny=99,maxy=-9;
s.cells.forEach(([x,y])=>{minx=Math.min(minx,x);maxx=Math.max(maxx,x);miny=Math.min(miny,y);maxy=Math.max(maxy,y);});
const w=(maxx-minx+1), h=(maxy-miny+1);
const ox=(nextCv.width/cx - w)/2 - minx;
const oy=(nextCv.height/cx - h)/2 - miny;
s.cells.forEach(([x,y])=> cellRect(cx, x+ox, y+oy, s.color, nctx));
}
function updateStats(){
scoreEl.textContent=score;
linesEl.textContent=lines;
levelEl.textContent=level;
}
// ---------- loop ----------
function tick(){
if(!running||paused||over) return;
drop();
dropTimer=setTimeout(tick, dropInterval);
}
function restartTimer(){
clearTimeout(dropTimer);
if(running&&!paused&&!over) dropTimer=setTimeout(tick, dropInterval);
}
function startGame(){
grid=emptyGrid();
score=0; lines=0; level=1; dropInterval=1000;
running=true; paused=false; over=false;
nextType=TYPES[Math.floor(Math.random()*TYPES.length)];
updateStats();
spawn(); draw();
overlay.classList.add("hidden");
clearTimeout(dropTimer);
dropTimer=setTimeout(tick, dropInterval);
}
function togglePause(){
if(!running||over) return;
paused=!paused;
if(paused){
clearTimeout(dropTimer);
showOverlay("PAUSED","Press P or the button to resume.","Resume");
} else {
overlay.classList.add("hidden");
restartTimer();
}
}
function gameOver(){
over=true; running=false;
clearTimeout(dropTimer);
showOverlay("GAME OVER","Score: "+score+" • Lines: "+lines,"Play Again");
}
function showOverlay(t,txt,btn){
ovTitle.textContent=t; ovText.textContent=txt; startBtn.textContent=btn;
overlay.classList.remove("hidden");
}
startBtn.addEventListener("click", ()=>{
if(paused && running && !over){ togglePause(); }
else startGame();
});
document.addEventListener("keydown",(e)=>{
if(e.key==="p"||e.key==="P"){ e.preventDefault(); togglePause(); return; }
if(!running||paused||over) return;
switch(e.key){
case "ArrowLeft": e.preventDefault(); move(-1); break;
case "ArrowRight": e.preventDefault(); move(1); break;
case "ArrowUp": e.preventDefault(); tryRotate(); break;
case "ArrowDown": e.preventDefault(); drop(); score+=1; updateStats(); restartTimer(); break;
case " ": e.preventDefault(); hardDrop(); restartTimer(); break;
}
});
// initial idle board
grid=emptyGrid(); draw(); nextType=TYPES[0]; drawNext();
})();
</script>
</body>
</html>