{
  "id": "game-2048",
  "name": "2048",
  "category": "gaming",
  "tags": ["2048", "slide game", "tile game", "number merge"],
  "description": "2048 sliding tile game with score.",
  "triggers": ["2048", "slide game", "tile game", "number merge"],
  "defaultSize": { "w": 4, "h": 4 },
  "source": "const Game2048 = () => {\n  const [board, setBoard] = React.useState(null);\n  const [score, setScore] = React.useState(0);\n  const [best, setBest] = React.useState(() => Number(localStorage.getItem('best2048')) || 0);\n  const [over, setOver] = React.useState(false);\n\n  const getColor = (v) => {\n    const colors = {\n      0: '#cdc1b4', 2: '#eee4da', 4: '#ede0c8', 8: '#f2b179',\n      16: '#f59563', 32: '#f67c5f', 64: '#f65e3b', 128: '#edcf72',\n      256: '#edcc61', 512: '#edc850', 1024: '#edc53f', 2048: '#edc22e'\n    };\n    return colors[v] || '#3c3a32';\n  };\n  const getTextColor = (v) => v > 4 ? '#f9f6f2' : '#776e65';\n\n  const emptyBoard = () => Array(4).fill(null).map(() => Array(4).fill(0));\n\n  const addRandom = (b) => {\n    const empty = [];\n    for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) if (!b[r][c]) empty.push([r, c]);\n    if (!empty.length) return b;\n    const [r, c] = empty[Math.floor(Math.random() * empty.length)];\n    const nb = b.map(row => [...row]);\n    nb[r][c] = Math.random() < 0.9 ? 2 : 4;\n    return nb;\n  };\n\n  const init = () => {\n    let b = emptyBoard();\n    b = addRandom(addRandom(b));\n    setBoard(b);\n    setScore(0);\n    setOver(false);\n  };\n\n  React.useEffect(() => {\n    init();\n  }, []);\n\n  const slideRow = (row) => {\n    let arr = row.filter(v => v);\n    for (let i = 0; i < arr.length - 1; i++) {\n      if (arr[i] === arr[i + 1]) {\n        arr[i] *= 2;\n        setScore(s => s + arr[i]);\n        arr[i + 1] = 0;\n      }\n    }\n    arr = arr.filter(v => v);\n    while (arr.length < 4) arr.push(0);\n    return arr;\n  };\n\n  const move = (dir) => {\n    if (over || !board) return;\n    let b = board.map(r => [...r]);\n    let moved = false;\n\n    if (dir === 'left') {\n      for (let r = 0; r < 4; r++) {\n        const newRow = slideRow(b[r]);\n        if (newRow.some((v, i) => v !== b[r][i])) moved = true;\n        b[r] = newRow;\n      }\n    } else if (dir === 'right') {\n      for (let r = 0; r < 4; r++) {\n        const newRow = slideRow(b[r].slice().reverse()).reverse();\n        if (newRow.some((v, i) => v !== b[r][i])) moved = true;\n        b[r] = newRow;\n      }\n    } else if (dir === 'up') {\n      for (let c = 0; c < 4; c++) {\n        const col = [b[0][c], b[1][c], b[2][c], b[3][c]];\n        const newCol = slideRow(col);\n        if (newCol.some((v, i) => v !== col[i])) moved = true;\n        for (let r = 0; r < 4; r++) b[r][c] = newCol[r];\n      }\n    } else if (dir === 'down') {\n      for (let c = 0; c < 4; c++) {\n        const col = [b[0][c], b[1][c], b[2][c], b[3][c]];\n        const newCol = slideRow(col.reverse()).reverse();\n        if (newCol.some((v, i) => v !== [b[0][c], b[1][c], b[2][c], b[3][c]][i])) moved = true;\n        for (let r = 0; r < 4; r++) b[r][c] = newCol[r];\n      }\n    }\n\n    if (moved) {\n      b = addRandom(b);\n      setBoard(b);\n      const hasEmpty = b.some(r => r.some(v => !v));\n      const canMerge = b.some((r, i) => r.some((v, j) =>\n        (j < 3 && v === b[i][j+1]) || (i < 3 && v === b[i+1][j])\n      ));\n      if (!hasEmpty && !canMerge) setOver(true);\n    }\n  };\n\n  React.useEffect(() => {\n    const handler = (e) => {\n      const keys = { ArrowLeft: 'left', ArrowRight: 'right', ArrowUp: 'up', ArrowDown: 'down' };\n      if (keys[e.key]) { e.preventDefault(); move(keys[e.key]); }\n    };\n    window.addEventListener('keydown', handler);\n    return () => window.removeEventListener('keydown', handler);\n  }, [board, over]);\n\n  React.useEffect(() => {\n    if (score > best) {\n      setBest(score);\n      localStorage.setItem('best2048', score);\n    }\n  }, [score]);\n\n  if (!board) return null;\n\n  return React.createElement('div', { style: { fontFamily: 'system-ui, sans-serif', padding: '16px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '12px', height: '100%', overflow: 'auto' } },\n    React.createElement('h3', { style: { margin: 0, fontSize: '16px', fontWeight: 600, color: '#111827' } }, '2048'),\n    React.createElement('div', { style: { display: 'flex', gap: '16px' } },\n      React.createElement('div', { style: { textAlign: 'center' } },\n        React.createElement('div', { style: { fontSize: '11px', color: '#6b7280', fontWeight: 600 } }, 'SCORE'),\n        React.createElement('div', { style: { fontSize: '18px', fontWeight: 700, color: '#111827' } }, score)\n      ),\n      React.createElement('div', { style: { textAlign: 'center' } },\n        React.createElement('div', { style: { fontSize: '11px', color: '#6b7280', fontWeight: 600 } }, 'BEST'),\n        React.createElement('div', { style: { fontSize: '18px', fontWeight: 700, color: '#111827' } }, best)\n      )\n    ),\n    React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, 60px)', gap: '6px', background: '#bbada0', padding: '6px', borderRadius: '6px', position: 'relative' } },\n      board.map((row, r) => row.map((cell, c) =>\n        React.createElement('div', {\n          key: r + '-' + c,\n          style: {\n            width: '60px',\n            height: '60px',\n            borderRadius: '4px',\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center',\n            fontSize: cell > 512 ? '18px' : '22px',\n            fontWeight: 700,\n            background: getColor(cell),\n            color: getTextColor(cell),\n            transition: 'all 0.15s ease'\n          }\n        }, cell || '')\n      )),\n      over && React.createElement('div', { style: { position: 'absolute', inset: 0, background: 'rgba(255,255,255,0.85)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', borderRadius: '6px', gap: '8px' } },\n        React.createElement('div', { style: { fontSize: '24px', fontWeight: 700, color: '#776e65' } }, 'Game Over!'),\n        React.createElement('button', { onClick: init, style: { fontSize: '13px', padding: '8px 20px', borderRadius: '4px', border: 'none', background: '#8f7a66', color: '#f9f6f2', cursor: 'pointer', fontWeight: 600 } }, 'Try Again')\n      )\n    ),\n    React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 40px)', gridTemplateRows: 'repeat(2, 36px)', gap: '4px' } },\n      React.createElement('div'),\n      React.createElement('button', { onClick: () => move('up'), style: { width: '40px', height: '36px', borderRadius: '4px', border: '1px solid #d1d5db', background: '#f3f4f6', cursor: 'pointer', fontSize: '16px' } }, '▲'),\n      React.createElement('div'),\n      React.createElement('button', { onClick: () => move('left'), style: { width: '40px', height: '36px', borderRadius: '4px', border: '1px solid #d1d5db', background: '#f3f4f6', cursor: 'pointer', fontSize: '16px' } }, '◀'),\n      React.createElement('button', { onClick: () => move('down'), style: { width: '40px', height: '36px', borderRadius: '4px', border: '1px solid #d1d5db', background: '#f3f4f6', cursor: 'pointer', fontSize: '16px' } }, '▼'),\n      React.createElement('button', { onClick: () => move('right'), style: { width: '40px', height: '36px', borderRadius: '4px', border: '1px solid #d1d5db', background: '#f3f4f6', cursor: 'pointer', fontSize: '16px' } }, '▶')\n    ),\n    React.createElement('button', { onClick: init, style: { fontSize: '12px', padding: '6px 14px', borderRadius: '4px', border: 'none', background: '#6b7280', color: '#fff', cursor: 'pointer', fontWeight: 600 } }, 'New Game')\n  );\n};\nrender(<Game2048/>);",
  "placeholders": []
}
