unfazed

← examples

Minesweeper

Jun 12, 2026 GPT-5.5 Codex CLI
app.unfazed.dev/classic-minesweeper-9x9 open (opens in new tab)
session
user

classic minesweeper: 9x9 grid with 10 mines, right-click to flag, timer and mine counter, dark minimal look. build it as a single static page and deploy it on unfazed using the mcp tools, then send me the public link.

I’ll build this directly as a static single-page app and deploy it through Unfazed. I’m first loading the deployment tool contract so the service matches what Unfazed expects.

I found the Unfazed static deployment tools. I’m creating a compact index.html with all CSS and JavaScript inline, then I’ll publish it with a stable alias.

create_workspace(name: "classic-minesweeper")
input
{
  "name": "classic-minesweeper"
}
result
{
  "created_at": "2026-06-12T04:51:09Z",
  "id": "98f148f2-f5f0-45c0-afa8-9809acccd2d6",
  "name": "classic-minesweeper",
  "updated_at": "2026-06-12T04:51:09Z"
}
write_workspace_text_file(workspace_id: "98f148f2-f5f0-45c0-afa8-9809acccd2d6", path: "index.html"…)
input
{
  "workspace_id": "98f148f2-f5f0-45c0-afa8-9809acccd2d6",
  "path": "index.html",
  "text": "\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n  \u003cmeta charset=\"utf-8\"\u003e\n  \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n  \u003ctitle\u003eClassic Minesweeper\u003c/title\u003e\n  \u003cstyle\u003e\n    :root {\n      color-scheme: dark;\n      --bg: #0d0f12;\n      --panel: #171a1f;\n      --panel-2: #1f242b;\n      --cell: #242a32;\n      --cell-hover: #2b333d;\n      --open: #12151a;\n      --line: #343b45;\n      --text: #e8edf2;\n      --muted: #8f9aa8;\n      --accent: #38bdf8;\n      --danger: #f87171;\n      --flag: #fbbf24;\n      --shadow: rgba(0, 0, 0, .32);\n    }\n\n    * { box-sizing: border-box; }\n\n    html, body { height: 100%; }\n\n    body {\n      margin: 0;\n      min-height: 100%;\n      display: grid;\n      place-items: center;\n      background: radial-gradient(circle at 50% 0%, #181d24 0, var(--bg) 42rem);\n      color: var(--text);\n      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n    }\n\n    main {\n      width: min(92vw, 430px);\n      padding: 20px;\n      border: 1px solid #252b34;\n      border-radius: 8px;\n      background: rgba(23, 26, 31, .92);\n      box-shadow: 0 20px 60px var(--shadow);\n    }\n\n    header {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      gap: 12px;\n      margin-bottom: 14px;\n    }\n\n    h1 {\n      margin: 0;\n      font-size: 1.1rem;\n      font-weight: 650;\n      letter-spacing: 0;\n    }\n\n    .stats {\n      display: flex;\n      gap: 8px;\n      align-items: center;\n    }\n\n    .stat, .reset {\n      height: 36px;\n      min-width: 58px;\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      border: 1px solid var(--line);\n      border-radius: 6px;\n      background: #111419;\n      color: var(--text);\n      font-variant-numeric: tabular-nums;\n      font-weight: 700;\n    }\n\n    .reset {\n      min-width: 42px;\n      cursor: pointer;\n      font-size: 1rem;\n    }\n\n    .reset:hover { background: #1a2028; }\n    .reset:active { transform: translateY(1px); }\n\n    .board {\n      width: 100%;\n      aspect-ratio: 1;\n      display: grid;\n      grid-template-columns: repeat(9, 1fr);\n      gap: 4px;\n      padding: 4px;\n      background: #0b0d10;\n      border: 1px solid var(--line);\n      border-radius: 8px;\n      touch-action: manipulation;\n      user-select: none;\n    }\n\n    .cell {\n      width: 100%;\n      aspect-ratio: 1;\n      display: grid;\n      place-items: center;\n      border: 1px solid #303844;\n      border-radius: 5px;\n      background: linear-gradient(180deg, var(--cell), #1d232b);\n      color: var(--text);\n      cursor: pointer;\n      font: 800 clamp(.95rem, 5vw, 1.35rem) / 1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n      padding: 0;\n    }\n\n    .cell:hover { background: var(--cell-hover); }\n    .cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\n\n    .cell.open {\n      cursor: default;\n      border-color: #1d2229;\n      background: var(--open);\n      box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .02);\n    }\n\n    .cell.flagged { color: var(--flag); }\n    .cell.mine { color: var(--danger); background: #2b1518; border-color: #5c252b; }\n    .cell.bad-flag { color: var(--danger); background: #291d12; }\n\n    .n1 { color: #60a5fa; }\n    .n2 { color: #34d399; }\n    .n3 { color: #fb7185; }\n    .n4 { color: #a78bfa; }\n    .n5 { color: #f59e0b; }\n    .n6 { color: #22d3ee; }\n    .n7 { color: #e5e7eb; }\n    .n8 { color: #9ca3af; }\n\n    .status {\n      min-height: 24px;\n      margin-top: 12px;\n      color: var(--muted);\n      text-align: center;\n      font-size: .92rem;\n    }\n\n    @media (max-width: 420px) {\n      body { place-items: start center; padding-top: 18px; }\n      main { width: calc(100vw - 20px); padding: 12px; }\n      header { gap: 8px; }\n      h1 { font-size: 1rem; }\n      .stat, .reset { height: 34px; min-width: 50px; }\n      .board { gap: 3px; }\n    }\n  \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n  \u003cmain aria-label=\"Minesweeper game\"\u003e\n    \u003cheader\u003e\n      \u003ch1\u003eMinesweeper\u003c/h1\u003e\n      \u003cdiv class=\"stats\" aria-label=\"Game stats\"\u003e\n        \u003cdiv class=\"stat\" id=\"mineCounter\" aria-label=\"Mines remaining\"\u003e010\u003c/div\u003e\n        \u003cbutton class=\"reset\" id=\"reset\" type=\"button\" aria-label=\"New game\" title=\"New game\"\u003e↻\u003c/button\u003e\n        \u003cdiv class=\"stat\" id=\"timer\" aria-label=\"Timer\"\u003e000\u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/header\u003e\n    \u003csection class=\"board\" id=\"board\" aria-label=\"9 by 9 minesweeper board\"\u003e\u003c/section\u003e\n    \u003cdiv class=\"status\" id=\"status\" aria-live=\"polite\"\u003eLeft-click to reveal. Right-click to flag.\u003c/div\u003e\n  \u003c/main\u003e\n\n  \u003cscript\u003e\n    (() =\u003e {\n      const SIZE = 9;\n      const MINES = 10;\n      const boardEl = document.getElementById('board');\n      const mineCounterEl = document.getElementById('mineCounter');\n      const timerEl = document.getElementById('timer');\n      const statusEl = document.getElementById('status');\n      const resetEl = document.getElementById('reset');\n\n      let cells;\n      let minesPlaced;\n      let gameOver;\n      let revealedCount;\n      let flags;\n      let seconds;\n      let timerId;\n\n      function pad(value) {\n        return String(Math.min(value, 999)).padStart(3, '0');\n      }\n\n      function index(row, col) {\n        return row * SIZE + col;\n      }\n\n      function neighbors(cellIndex) {\n        const row = Math.floor(cellIndex / SIZE);\n        const col = cellIndex % SIZE;\n        const result = [];\n        for (let dr = -1; dr \u003c= 1; dr++) {\n          for (let dc = -1; dc \u003c= 1; dc++) {\n            if (!dr \u0026\u0026 !dc) continue;\n            const nr = row + dr;\n            const nc = col + dc;\n            if (nr \u003e= 0 \u0026\u0026 nr \u003c SIZE \u0026\u0026 nc \u003e= 0 \u0026\u0026 nc \u003c SIZE) result.push(index(nr, nc));\n          }\n        }\n        return result;\n      }\n\n      function startTimer() {\n        if (timerId) return;\n        timerId = setInterval(() =\u003e {\n          seconds += 1;\n          timerEl.textContent = pad(seconds);\n        }, 1000);\n      }\n\n      function stopTimer() {\n        clearInterval(timerId);\n        timerId = null;\n      }\n\n      function updateMineCounter() {\n        mineCounterEl.textContent = pad(MINES - flags);\n      }\n\n      function placeMines(firstIndex) {\n        const blocked = new Set([firstIndex]);\n        const pool = [];\n        for (let i = 0; i \u003c SIZE * SIZE; i++) {\n          if (!blocked.has(i)) pool.push(i);\n        }\n        for (let i = pool.length - 1; i \u003e 0; i--) {\n          const j = Math.floor(Math.random() * (i + 1));\n          [pool[i], pool[j]] = [pool[j], pool[i]];\n        }\n        pool.slice(0, MINES).forEach(i =\u003e cells[i].mine = true);\n        cells.forEach((cell, i) =\u003e {\n          cell.count = neighbors(i).filter(n =\u003e cells[n].mine).length;\n        });\n        minesPlaced = true;\n      }\n\n      function drawCell(i) {\n        const cell = cells[i];\n        const btn = cell.el;\n        btn.className = 'cell';\n        btn.textContent = '';\n        btn.setAttribute('aria-label', `Cell ${Math.floor(i / SIZE) + 1}, ${i % SIZE + 1}`);\n\n        if (cell.open) {\n          btn.classList.add('open');\n          btn.disabled = true;\n          if (cell.mine) {\n            btn.classList.add('mine');\n            btn.textContent = '*';\n          } else if (cell.count \u003e 0) {\n            btn.classList.add(`n${cell.count}`);\n            btn.textContent = cell.count;\n          }\n          return;\n        }\n\n        btn.disabled = false;\n        if (cell.flagged) {\n          btn.classList.add('flagged');\n          btn.textContent = '⚑';\n        }\n      }\n\n      function reveal(i) {\n        const cell = cells[i];\n        if (cell.open || cell.flagged || gameOver) return;\n        if (!minesPlaced) {\n          placeMines(i);\n          startTimer();\n        }\n\n        cell.open = true;\n        revealedCount += 1;\n\n        if (cell.mine) {\n          lose(i);\n          return;\n        }\n\n        if (cell.count === 0) {\n          neighbors(i).forEach(n =\u003e {\n            if (!cells[n].open \u0026\u0026 !cells[n].flagged \u0026\u0026 !cells[n].mine) reveal(n);\n          });\n        }\n\n        drawAll();\n        checkWin();\n      }\n\n      function toggleFlag(i) {\n        const cell = cells[i];\n        if (cell.open || gameOver) return;\n        cell.flagged = !cell.flagged;\n        flags += cell.flagged ? 1 : -1;\n        updateMineCounter();\n        drawCell(i);\n      }\n\n      function lose(triggerIndex) {\n        gameOver = true;\n        stopTimer();\n        cells.forEach((cell, i) =\u003e {\n          if (cell.mine) cell.open = true;\n          drawCell(i);\n          if (cell.flagged \u0026\u0026 !cell.mine) cell.el.classList.add('bad-flag');\n        });\n        cells[triggerIndex].el.classList.add('mine');\n        statusEl.textContent = 'Mine hit. Start a new game.';\n      }\n\n      function checkWin() {\n        if (revealedCount !== SIZE * SIZE - MINES) return;\n        gameOver = true;\n        stopTimer();\n        cells.forEach((cell, i) =\u003e {\n          if (cell.mine \u0026\u0026 !cell.flagged) {\n            cell.flagged = true;\n            flags += 1;\n          }\n          drawCell(i);\n        });\n        updateMineCounter();\n        statusEl.textContent = `Cleared in ${seconds} second${seconds === 1 ? '' : 's'}.`;\n      }\n\n      function drawAll() {\n        cells.forEach((_, i) =\u003e drawCell(i));\n      }\n\n      function newGame() {\n        stopTimer();\n        cells = [];\n        minesPlaced = false;\n        gameOver = false;\n        revealedCount = 0;\n        flags = 0;\n        seconds = 0;\n        boardEl.replaceChildren();\n        timerEl.textContent = '000';\n        updateMineCounter();\n        statusEl.textContent = 'Left-click to reveal. Right-click to flag.';\n\n        for (let i = 0; i \u003c SIZE * SIZE; i++) {\n          const btn = document.createElement('button');\n          btn.type = 'button';\n          btn.className = 'cell';\n          btn.dataset.index = i;\n          btn.addEventListener('click', () =\u003e reveal(i));\n          btn.addEventListener('contextmenu', event =\u003e {\n            event.preventDefault();\n            toggleFlag(i);\n          });\n          cells.push({ el: btn, mine: false, count: 0, open: false, flagged: false });\n          boardEl.append(btn);\n        }\n        drawAll();\n      }\n\n      resetEl.addEventListener('click', newGame);\n      boardEl.addEventListener('contextmenu', event =\u003e event.preventDefault());\n      newGame();\n    })();\n  \u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e"
}
result
{
  "path": "index.html",
  "sha256": "3dfe552fa17567b7d9322de46709fc628bdc9b66062dd9ce558c55765799cbed",
  "size": "10.1 KB",
  "size_bytes": 10303,
  "updated_at": "2026-06-12T04:52:12Z"
}

The page is written. I’m deploying it now as static files; the URL should be stable under the alias once the deployment completes.

create_static_deployment(workspace_id: "98f148f2-f5f0-45c0-afa8-9809acccd2d6", …) error
input
{
  "workspace_id": "98f148f2-f5f0-45c0-afa8-9809acccd2d6",
  "alias": "classic-minesweeper",
  "spa_mode": false
}
result
alias "classic-minesweeper" is already in use

That alias is already taken, so I’m redeploying with a more specific stable alias.

create_static_deployment(workspace_id: "98f148f2-f5f0-45c0-afa8-9809acccd2d6", …)
input
{
  "workspace_id": "98f148f2-f5f0-45c0-afa8-9809acccd2d6",
  "alias": "classic-minesweeper-9x9",
  "spa_mode": false
}
result
{
  "alias": "classic-minesweeper-9x9",
  "created_at": "2026-06-12T04:52:21Z",
  "files_count": 1,
  "id": "b69cc596-ac84-4ac2-a5b5-325670868db7",
  "kind": "STATIC",
  "service_mode": "ON_DEMAND",
  "size": "10.1 KB",
  "size_bytes": 10303,
  "spa_mode": false,
  "status": "READY",
  "url": "https://app.unfazed.dev/classic-minesweeper-9x9/",
  "workspace_id": "98f148f2-f5f0-45c0-afa8-9809acccd2d6"
}

Built and deployed the static Minesweeper page.

Public link: https://app.unfazed.dev/classic-minesweeper-9x9

It includes the requested 9x9 board, 10 mines, right-click flagging, timer, mine counter, reset button, and a dark minimal UI.

result
app.unfazed.dev/classic-minesweeper-9x9 open (opens in new tab)