{
  "id": "chatcmpl-2eb725fbafbd",
  "object": "chat.completion",
  "created": 1787806277,
  "model": "unsloth/Qwen3.8-27B-GGUF",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<title>Block Fall</title>\n<style>\n  * { margin: 0; padding: 0; box-sizing: border-box; }\n  html, body {\n    height: 100%;\n    background: #0d0f14;\n    color: #e8e8f0;\n    font-family: 'Courier New', monospace;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n  }\n  #wrap {\n    display: flex;\n    gap: 24px;\n    align-items: flex-start;\n  }\n  #board {\n    background: #14161d;\n    border: 2px solid #2a2e3a;\n    box-shadow: 0 0 40px rgba(0,0,0,.6), inset 0 0 20px rgba(0,0,0,.5);\n    display: block;\n  }\n  #panel {\n    display: flex;\n    flex-direction: column;\n    gap: 16px;\n    width: 190px;\n  }\n  .box {\n    background: #14161d;\n    border: 2px solid #2a2e3a;\n    padding: 12px;\n    border-radius: 4px;\n  }\n  .box h3 {\n    font-size: 12px;\n    letter-spacing: 2px;\n    color: #7a8199;\n    margin-bottom: 8px;\n    text-transform: uppercase;\n  }\n  .box .val {\n    font-size: 22px;\n    color: #ffffff;\n  }\n  #next {\n    display: block;\n    margin: 0 auto;\n    background: #0f1117;\n    border: 1px solid #2a2e3a;\n  }\n  #controls {\n    font-size: 12px;\n    line-height: 1.9;\n    color: #9aa1b8;\n  }\n  #controls b { color: #dfe3ee; }\n  #gameover {\n    position: fixed;\n    inset: 0;\n    background: rgba(8,9,12,.82);\n    display: none;\n    align-items: center;\n    justify-content: center;\n    flex-direction: column;\n    gap: 18px;\n    z-index: 10;\n  }\n  #gameover h1 {\n    font-size: 48px;\n    letter-spacing: 6px;\n    color: #ff5566;\n    text-shadow: 0 0 30px rgba(255,85,102,.5);\n  }\n  #gameover p {\n    color: #cfd4e4;\n    font-size: 16px;\n  }\n  #gameover .final {\n    font-size: 20px;\n    color: #ffffff;\n  }\n  #title {\n    font-size: 20px;\n    letter-spacing: 4px;\n    text-align: center;\n    color: #56c1ff;\n    text-shadow: 0 0 14px rgba(86,193,255,.4);\n  }\n</style>\n</head>\n<body>\n\n<div id=\"wrap\">\n  <canvas id=\"board\" width=\"300\" height=\"600\"></canvas>\n  <div id=\"panel\">\n    <div class=\"box\" id=\"title\">BLOCK FALL</div>\n    <div class=\"box\">\n      <h3>Score</h3>\n      <div class=\"val\" id=\"score\">0</div>\n    </div>\n    <div class=\"box\">\n      <h3>Lines</h3>\n      <div class=\"val\" id=\"lines\">0</div>\n    </div>\n    <div class=\"box\">\n      <h3>Level</h3>\n      <div class=\"val\" id=\"level\">1</div>\n    </div>\n    <div class=\"box\">\n      <h3>Next</h3>\n      <canvas id=\"next\" width=\"120\" height=\"120\"></canvas>\n    </div>\n    <div class=\"box\" id=\"controls\">\n      <b>&larr; &rarr;</b> move<br>\n      <b>&uarr;</b> rotate<br>\n      <b>&darr;</b> soft drop<br>\n      <b>space</b> hard drop<br>\n      <b>P</b> pause\n    </div>\n  </div>\n</div>\n\n<div id=\"gameover\">\n  <h1>GAME OVER</h1>\n  <div class=\"final\" id=\"finalscore\"></div>\n  <p>Press <b>R</b> to restart</p>\n</div>\n\n<script>\n(function () {\n  'use strict';\n\n  // ----- Constants -----\n  var COLS = 10, ROWS = 20, CELL = 30;\n  var boardCanvas = document.getElementById('board');\n  var bctx = boardCanvas.getContext('2d');\n  var nextCanvas = document.getElementById('next');\n  var nctx = nextCanvas.getContext('2d');\n  var scoreEl = document.getElementById('score');\n  var linesEl = document.getElementById('lines');\n  var levelEl = document.getElementById('level');\n  var overEl = document.getElementById('gameover');\n  var finalEl = document.getElementById('finalscore');\n\n  var SHAPES = {\n    I: { color: '#4fc3f7', cells: [[0,1],[1,1],[2,1],[3,1]] },\n    O: { color: '#ffd54f', cells: [[0,0],[1,0],[0,1],[1,1]] },\n    T: { color: '#ba68c8', cells: [[1,0],[0,1],[1,1],[2,1]] },\n    S: { color: '#81c784', cells: [[1,0],[2,0],[0,1],[1,1]] },\n    Z: { color: '#ef5350', cells: [[0,0],[1,0],[1,1],[2,1]] },\n    J: { color: '#5c6bc0', cells: [[0,0],[0,1],[1,1],[2,1]] },\n    L: { color: '#ffa726', cells: [[2,0],[0,1],[1,1],[2,1]] }\n  };\n  var TYPES = Object.keys(SHAPES);\n\n  // ----- State -----\n  var grid, piece, nextType, score, lines, level, dropInterval, dropTimer, lastTime;\n  var gameOver, paused, rafId = null;\n\n  function makeGrid() {\n    var g = [];\n    for (var r = 0; r < ROWS; r++) {\n      g.push(new Array(COLS).fill(null));\n    }\n    return g;\n  }\n\n  function randType() {\n    return TYPES[Math.floor(Math.random() * TYPES.length)];\n  }\n\n  function makePiece(type) {\n    var def = SHAPES[type];\n    var cells = def.cells.map(function (c) { return [c[0], c[1]]; });\n    return {\n      type: type,\n      color: def.color,\n      cells: cells,\n      x: Math.floor(COLS / 2) - 2,\n      y: type === 'I' ? -1 : 0\n    };\n  }\n\n  function cellsOf(p, dx, dy, rotCells) {\n    var src = rotCells || p.cells;\n    return src.map(function (c) { return [p.x + c[0] + (dx || 0), p.y + c[1] + (dy || 0)]; });\n  }\n\n  function collides(cells) {\n    for (var i = 0; i < cells.length; i++) {\n      var x = cells[i][0], y = cells[i][1];\n      if (x < 0 || x >= COLS || y >= ROWS) return true;\n      if (y >= 0 && grid[y][x]) return true;\n    }\n    return false;\n  }\n\n  // Rotate clockwise around piece's own 4x4-ish footprint.\n  // Standard rotation: (x, y) -> (size-1-y, x), per piece bounding box.\n  function rotateCells(cells, dir) {\n    // find bounding box\n    var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n    cells.forEach(function (c) {\n      if (c[0] < minX) minX = c[0];\n      if (c[0] > maxX) maxX = c[0];\n      if (c[1] < minY) minY = c[1];\n      if (c[1] > maxY) maxY = c[1];\n    });\n    var w = maxX - minX + 1, h = maxY - minY + 1;\n    return cells.map(function (c) {\n      var x = c[0] - minX, y = c[1] - minY;\n      var nx, ny;\n      if (dir > 0) { nx = h - 1 - y; ny = x; }\n      else         { nx = y; ny = w - 1 - x; }\n      return [minX + nx, minY + ny];\n    });\n  }\n\n  function tryRotate() {\n    var dir = 1;\n    var rotated = rotateCells(piece.cells, dir);\n    // Wall kicks: try offsets in order\n    var kicks = [[0,0],[1,0],[-1,0],[2,0],[-2,0],[0,-1],[1,-1],[-1,-1],[0,1],[1,1],[-1,1]];\n    for (var i = 0; i < kicks.length; i++) {\n      var cells = cellsOf(piece, kicks[i][0], kicks[i][1], rotated);\n      if (!collides(cells)) {\n        piece.cells = rotated;\n        piece.x += kicks[i][0];\n        piece.y += kicks[i][1];\n        return true;\n      }\n    }\n    return false;\n  }\n\n  function move(dx) {\n    var cells = cellsOf(piece, dx, 0);\n    if (!collides(cells)) piece.x += dx;\n  }\n\n  function dropSoft() {\n    var cells = cellsOf(piece, 0, 1);\n    if (!collides(cells)) {\n      piece.y += 1;\n      if (!autoSoftDropHeld) score += 0; // soft drop via key handled below\n      return true;\n    }\n    lockPiece();\n    return false;\n  }\n\n  function hardDrop() {\n    var dist = 0;\n    while (!collides(cellsOf(piece, 0, 1))) { piece.y += 1; dist++; }\n    score += dist * 2;\n    lockPiece();\n    dropTimer = 0;\n  }\n\n  function ghostCells() {\n    var y = piece.y;\n    while (!collides(piece.cells.map(function (c) { return [piece.x + c[0], y + c[1] + (1)]; }))) y++;\n    return piece.cells.map(function (c) { return [piece.x + c[0], y + c[1]]; });\n  }\n\n  function lockPiece() {\n    var cells = piece.cells;\n    var toppedOut = false;\n    for (var i = 0; i < cells.length; i++) {\n      var x = piece.x + cells[i][0], y = piece.y + cells[i][1];\n      if (y < 0) { toppedOut = true; continue; }\n      grid[y][x] = piece.color;\n    }\n    if (toppedOut) {\n      endGame();\n      return;\n    }\n    clearLines();\n    spawnPiece();\n  }\n\n  function clearLines() {\n    var cleared = 0;\n    for (var r = ROWS - 1; r >= 0; r--) {\n      if (grid[r].every(function (c) { return c !== null; })) {\n        grid.splice(r, 1);\n        grid.unshift(new Array(COLS).fill(null));\n        cleared++;\n        r++; // re-check this row index\n      }\n    }\n    if (cleared > 0) {\n      var points = [0, 40, 100, 300, 1200];\n      score += points[cleared] * level;\n      lines += cleared;\n      level = Math.floor(lines / 10) + 1;\n      dropInterval = Math.max(90, 800 - (level - 1) * 70);\n      updateHUD();\n    }\n  }\n\n  function spawnPiece() {\n    piece = makePiece(nextType);\n    nextType = randType();\n    drawNext();\n    dropTimer = 0;\n    if (collides(piece.cells)) {\n      endGame();\n    }\n  }\n\n  function endGame() {\n    gameOver = true;\n    cancelAnimationFrame(rafId);\n    finalEl.textContent = 'Final score: ' + score;\n    overEl.style.display = 'flex';\n  }\n\n  function reset() {\n    grid = makeGrid();\n    score = 0; lines = 0; level = 1;\n    dropInterval = 800; dropTimer = 0; lastTime = null;\n    gameOver = false; paused = false;\n    overEl.style.display = 'none';\n    nextType = randType();\n    spawnPiece();\n    updateHUD();\n    rafId = requestAnimationFrame(loop);\n  }\n\n  function updateHUD() {\n    scoreEl.textContent = score;\n    linesEl.textContent = lines;\n    levelEl.textContent = level;\n  }\n\n  // ----- Rendering -----\n  function drawCell(ctx, x, y, size, color, ghost) {\n    if (ghost) {\n      ctx.strokeStyle = color;\n      ctx.lineWidth = 2;\n      ctx.globalAlpha = 0.35;\n      ctx.strokeRect(x * size + 2, y * size + 2, size - 4, size - 4);\n      ctx.globalAlpha = 1;\n      return;\n    }\n    ctx.fillStyle = color;\n    ctx.fillRect(x * size + 1, y * size + 1, size - 2, size - 2);\n    // subtle highlight\n    ctx.fillStyle = 'rgba(255,255,255,0.18)';\n    ctx.fillRect(x * size + 1, y * size + 1, size - 2, 4);\n    ctx.fillStyle = 'rgba(0,0,0,0.25)';\n    ctx.fillRect(x * size + 1, y * size + size - 5, size - 2, 4);\n  }\n\n  function render() {\n    bctx.clearRect(0, 0, boardCanvas.width, boardCanvas.height);\n\n    // grid lines\n    bctx.strokeStyle = 'rgba(255,255,255,0.04)';\n    bctx.lineWidth = 1;\n    for (var x = 1; x < COLS; x++) {\n      bctx.beginPath();\n      bctx.moveTo(x * CELL + .5, 0);\n      bctx.lineTo(x * CELL + .5, ROWS * CELL);\n      bctx.stroke();\n    }\n    for (var y = 1; y < ROWS; y++) {\n      bctx.beginPath();\n      bctx.moveTo(0, y * CELL + .5);\n      bctx.lineTo(COLS * CELL, y * CELL + .5);\n      bctx.stroke();\n    }\n\n    // settled blocks\n    for (var r = 0; r < ROWS; r++) {\n      for (var c = 0; c < COLS; c++) {\n        if (grid[r][c]) drawCell(bctx, c, r, CELL, grid[r][c], false);\n      }\n    }\n\n    if (piece && !gameOver) {\n      // ghost\n      var g = ghostCells();\n      for (var i = 0; i < g.length; i++) {\n        if (g[i][1] >= 0) drawCell(bctx, g[i][0], g[i][1], CELL, piece.color, true);\n      }\n      // active piece\n      for (var j = 0; j < piece.cells.length; j++) {\n        var px = piece.x + piece.cells[j][0], py = piece.y + piece.cells[j][1];\n        if (py >= 0) drawCell(bctx, px, py, CELL, piece.color, false);\n      }\n    }\n\n    if (paused && !gameOver) {\n      bctx.fillStyle = 'rgba(8,9,12,0.7)';\n      bctx.fillRect(0, 0, boardCanvas.width, boardCanvas.height);\n      bctx.fillStyle = '#e8e8f0';\n      bctx.font = 'bold 24px Courier New';\n      bctx.textAlign = 'center';\n      bctx.fillText('PAUSED', boardCanvas.width / 2, boardCanvas.height / 2);\n      bctx.textAlign = 'left';\n    }\n  }\n\n  function drawNext() {\n    nctx.clearRect(0, 0, nextCanvas.width, nextCanvas.height);\n    var def = SHAPES[nextType];\n    var size = 26;\n    // center piece\n    var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n    def.cells.forEach(function (c) {\n      if (c[0] < minX) minX = c[0];\n      if (c[0] > maxX) maxX = c[0];\n      if (c[1] < minY) minY = c[1];\n      if (c[1] > maxY) maxY = c[1];\n    });\n    var w = (maxX - minX + 1) * size;\n    var h = (maxY - minY + 1) * size;\n    var ox = (nextCanvas.width - w) / 2 - minX * size;\n    var oy = (nextCanvas.height - h) / 2 - minY * size;\n    def.cells.forEach(function (c) {\n      nctx.fillStyle = def.color;\n      nctx.fillRect(ox + c[0] * size + 1, oy + c[1] * size + 1, size - 2, size - 2);\n      nctx.fillStyle = 'rgba(255,255,255,0.18)';\n      nctx.fillRect(ox + c[0] * size + 1, oy + c[1] * size + 1, size - 2, 3);\n    });\n  }\n\n  // ----- Loop -----\n  function loop(t) {\n    if (gameOver) return;\n    if (lastTime === null) lastTime = t;\n    var dt = t - lastTime;\n    lastTime = t;\n    if (!paused) {\n      dropTimer += dt;\n      if (dropTimer >= dropInterval) {\n        dropTimer = 0;\n        dropSoft();\n      }\n      render();\n    }\n    rafId = requestAnimationFrame(loop);\n  }\n\n  var autoSoftDropHeld = false;\n\n  // ----- Input -----\n  document.addEventListener('keydown', function (e) {\n    var key = e.key;\n    if (gameOver) {\n      if (key === 'r' || key === 'R' || key === 'Enter') reset();\n      return;\n    }\n    if (key === 'p' || key === 'P') {\n      paused = !paused;\n      lastTime = null;\n      render();\n      return;\n    }\n    if (paused) return;\n\n    if (key === 'ArrowLeft')      { e.preventDefault(); move(-1); }\n    else if (key === 'ArrowRight'){ e.preventDefault(); move(1); }\n    else if (key === 'ArrowUp')   { e.preventDefault(); tryRotate(); }\n    else if (key === 'ArrowDown') {\n      e.preventDefault();\n      if (dropSoft()) score += 1;\n      updateHUD();\n      dropTimer = 0;\n    }\n    else if (key === ' ')         { e.preventDefault(); hardDrop(); updateHUD(); }\n\n    if (!gameOver) render();\n  });\n\n  // ----- Start -----\n  reset();\n})();\n</script>\n</body>\n</html>",
        "refusal": null,
        "reasoning_content": "Create a single HTML file containing a Tetris game. Use 7 standard tetrominoes, SRS-lite rotation with wall kicks (simple: try offsets 0, -1, +1, -2, +2). Score: 40/100/300/1200 \u00d7 level. Level up every 10 lines, speed goes up. Preview canvas. Game over overlay, restart with R/Enter. Dark style.\n\nWrite clean code.\n",
        "tool_calls": null
      },
      "finish_reason": "stop",
      "logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 212,
    "completion_tokens": 4947,
    "total_tokens": 5159,
    "prompt_tokens_details": {
      "cached_tokens": 0,
      "audio_tokens": 0
    },
    "completion_tokens_details": {
      "reasoning_tokens": 0,
      "audio_tokens": 0,
      "accepted_prediction_tokens": 0,
      "rejected_prediction_tokens": 0
    }
  },
  "system_fingerprint": null
}