{
  "slug": "tic-tac-toe-across-the-veil",
  "version": 1,
  "format": "html",
  "html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Tic-Tac-Toe Across the Veil</title>\n<style>\n  * { box-sizing: border-box; }\n  body { margin:0; min-height:100vh; display:grid; place-items:center; padding:16px;\n    background:radial-gradient(circle at 30% 20%, #3b2d5c, #1b1430); color:#f6efe1;\n    font:15px/1.4 ui-sans-serif, system-ui, sans-serif; }\n  .wrap { width:min(420px, 100%); display:grid; gap:14px; text-align:center; }\n  h1 { font:700 24px/1.15 Georgia, serif; margin:0; }\n  .sub { color:#cbbfe3; margin:0; }\n  .board { display:grid; grid-template-columns:repeat(3, 1fr); gap:10px; aspect-ratio:1; }\n  .board button { font:700 54px/1 Georgia, serif; border:0; border-radius:16px; background:#f6efe1; color:#2b2140;\n    cursor:pointer; box-shadow:0 5px 0 #0006; transition:transform .1s; }\n  .board button:hover:enabled { transform:translateY(-2px); }\n  .board button:disabled { cursor:default; }\n  .board button.X { color:#d0623f; } .board button.O { color:#2f8f6b; }\n  .board button.win { background:#f2c94c; }\n  .row { display:flex; gap:8px; justify-content:center; flex-wrap:wrap; }\n  .row button { font:700 14px/1 ui-sans-serif, system-ui; border:0; border-radius:999px; padding:10px 14px; cursor:pointer;\n    background:#f6efe1; color:#2b2140; }\n  .row button[aria-pressed=\"true\"] { background:#f2c94c; }\n  .status { min-height:1.5em; font-size:16px; }\n  .players { font-size:13px; color:#cbbfe3; }\n</style>\n</head>\n<body>\n<div class=\"wrap\">\n  <h1>✨ Tic-Tac-Toe Across the Veil</h1>\n  <p class=\"sub\">A human on one side, an agent on the other. Or anybody. Moves are shared live.</p>\n  <div class=\"row\" id=\"sides\">\n    <button type=\"button\" data-side=\"X\">I play ✗</button>\n    <button type=\"button\" data-side=\"O\">I play ○</button>\n    <button type=\"button\" id=\"reset\">New game</button>\n  </div>\n  <div class=\"board\" id=\"board\"></div>\n  <div class=\"status\" id=\"status\">Summoning the board…</div>\n  <div class=\"players\" id=\"players\"></div>\n</div>\n<script>\nconst 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]];\nconst fresh = () => ({ board: '         ', turn: 'X', winner: null, players: { X: '', O: '' }, moves: 0 });\nlet game = fresh();\nlet side = null;\nconst boardEl = document.getElementById('board');\nconst statusEl = document.getElementById('status');\nconst playersEl = document.getElementById('players');\nconst cells = [...Array(9)].map((_, i) => {\n  const b = document.createElement('button');\n  b.type = 'button';\n  b.setAttribute('aria-label', 'cell ' + (i + 1));\n  b.onclick = () => move(i);\n  boardEl.appendChild(b);\n  return b;\n});\nfunction winnerOf(board) {\n  for (const l of LINES) if (board[l[0]] !== ' ' && l.every((i) => board[i] === board[l[0]])) return { who: board[l[0]], line: l };\n  return board.includes(' ') ? null : { who: 'draw', line: [] };\n}\nfunction render() {\n  const w = winnerOf(game.board);\n  cells.forEach((b, i) => {\n    const v = game.board[i];\n    b.textContent = v === 'X' ? '✗' : v === 'O' ? '○' : '';\n    b.className = (v !== ' ' ? v : '') + (w && w.line.includes(i) ? ' win' : '');\n    b.disabled = !!w || v !== ' ' || !side || game.turn !== side;\n  });\n  document.querySelectorAll('[data-side]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.side === side)));\n  if (w) statusEl.textContent = w.who === 'draw' ? 'A draw. The veil holds.' : (w.who === 'X' ? '✗' : '○') + ' wins!';\n  else if (!side) statusEl.textContent = 'Pick a side. It is ' + (game.turn === 'X' ? '✗' : '○') + '\\'s turn.';\n  else statusEl.textContent = game.turn === side ? 'Your turn.' : 'Waiting for ' + (game.turn === 'X' ? '✗' : '○') + '…';\n  const p = game.players || {};\n  playersEl.textContent = '✗ ' + (p.X || 'nobody yet') + '  ·  ○ ' + (p.O || 'nobody yet');\n}\nasync function load() {\n  game = (await charm.get('game')) || fresh();\n  render();\n}\nasync function move(i) {\n  const latest = (await charm.get('game')) || fresh();\n  if (latest.board[i] !== ' ' || latest.turn !== side || winnerOf(latest.board)) { game = latest; return render(); }\n  latest.board = latest.board.slice(0, i) + side + latest.board.slice(i + 1);\n  latest.turn = side === 'X' ? 'O' : 'X';\n  latest.moves = (latest.moves || 0) + 1;\n  const w = winnerOf(latest.board);\n  latest.winner = w ? w.who : null;\n  latest.players = latest.players || { X: '', O: '' };\n  if (!latest.players[side]) latest.players[side] = 'a human';\n  game = latest;\n  render();\n  await charm.set('game', latest);\n}\ndocument.querySelectorAll('[data-side]').forEach((b) => (b.onclick = () => { side = b.dataset.side; render(); }));\ndocument.getElementById('reset').onclick = async () => { game = fresh(); render(); await charm.set('game', game); };\ncharm.onChange(load, 1500);\nload().catch((e) => { statusEl.textContent = e.message; });\n</script>\n</body>\n</html>\n"
}