{"spec_id":"chessboard-pieces","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// chessboard-pieces: Chess Board with Pieces for Position Diagrams\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-01\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: Fool's Mate, the fastest possible checkmate ----------------------\n// 1. f3 e5  2. g4 Qh4#\nconst FILES = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"];\nconst WHITE_GLYPH = { K: \"♔\", Q: \"♕\", R: \"♖\", B: \"♗\", N: \"♘\", P: \"♙\" };\nconst BLACK_GLYPH = { k: \"♚\", q: \"♛\", r: \"♜\", b: \"♝\", n: \"♞\", p: \"♟\" };\n\nconst pieces = {\n  a1: \"R\", b1: \"N\", c1: \"B\", d1: \"Q\", e1: \"K\", f1: \"B\", g1: \"N\", h1: \"R\",\n  a2: \"P\", b2: \"P\", c2: \"P\", d2: \"P\", e2: \"P\", h2: \"P\", f3: \"P\", g4: \"P\",\n  a7: \"p\", b7: \"p\", c7: \"p\", d7: \"p\", f7: \"p\", g7: \"p\", h7: \"p\", e5: \"p\",\n  a8: \"r\", b8: \"n\", c8: \"b\", e8: \"k\", f8: \"b\", g8: \"n\", h8: \"r\", h4: \"q\",\n};\n\n// Chess armies keep a fixed black/white identity regardless of page theme,\n// the same way an Imprint data color stays constant across light and dark.\nconst WHITE_PIECE = \"#F0EFE8\";\nconst BLACK_PIECE = \"#1A1A17\";\nconst isLightSquare = (file, rank) => (file + rank) % 2 === 1; // a1 dark, h1 light\nconst squareTint = (alpha) => (t.theme === \"light\" ? `rgba(26,26,23,${alpha})` : `rgba(240,239,232,${alpha})`);\nconst LIGHT_SQUARE = squareTint(0.05);\nconst DARK_SQUARE = squareTint(0.2);\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Board + piece rendering plugin ------------------------------------------\n// Chart.js has no native board/grid chart type; a \"scatter\" chart with an empty\n// dataset supplies the title plugin, theming and canvas lifecycle, while this\n// plugin does the actual drawing against chart.chartArea (Chart.js's own\n// plugin API — no external chartjs-chart-* package involved).\nconst chessBoardPlugin = {\n  id: \"chessBoard\",\n  afterDatasetsDraw(chart) {\n    const { ctx, chartArea } = chart;\n    const padLeft = chartArea.width * 0.045;\n    const padBottom = chartArea.height * 0.045;\n    const usableWidth = chartArea.width - padLeft;\n    const usableHeight = chartArea.height - padBottom;\n    const boardSize = Math.min(usableWidth, usableHeight);\n    const boardLeft = chartArea.left + padLeft + (usableWidth - boardSize) / 2;\n    const boardTop = chartArea.top + (usableHeight - boardSize) / 2;\n    const cell = boardSize / 8;\n\n    ctx.save();\n\n    // Squares\n    for (let file = 1; file <= 8; file++) {\n      for (let rank = 1; rank <= 8; rank++) {\n        const x = boardLeft + (file - 1) * cell;\n        const y = boardTop + (8 - rank) * cell;\n        ctx.fillStyle = isLightSquare(file, rank) ? LIGHT_SQUARE : DARK_SQUARE;\n        ctx.fillRect(x, y, cell, cell);\n      }\n    }\n\n    // Frame\n    ctx.strokeStyle = t.inkSoft;\n    ctx.lineWidth = 2;\n    ctx.strokeRect(boardLeft, boardTop, boardSize, boardSize);\n\n    // Coordinate labels\n    const labelSize = Math.round(cell * 0.16);\n    ctx.fillStyle = t.inkSoft;\n    ctx.font = `${labelSize}px \"DejaVu Sans\", sans-serif`;\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"top\";\n    for (let file = 1; file <= 8; file++) {\n      const x = boardLeft + (file - 1) * cell + cell / 2;\n      ctx.fillText(FILES[file - 1], x, boardTop + boardSize + labelSize * 0.4);\n    }\n    ctx.textAlign = \"right\";\n    ctx.textBaseline = \"middle\";\n    for (let rank = 1; rank <= 8; rank++) {\n      const y = boardTop + (8 - rank) * cell + cell / 2;\n      ctx.fillText(String(rank), boardLeft - labelSize * 0.5, y);\n    }\n\n    // Pieces\n    const pieceSize = Math.round(cell * 0.72);\n    ctx.font = `${pieceSize}px \"DejaVu Sans\", sans-serif`;\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"middle\";\n    ctx.lineWidth = pieceSize * 0.045;\n    for (const [square, code] of Object.entries(pieces)) {\n      const file = FILES.indexOf(square[0]) + 1;\n      const rank = Number(square[1]);\n      const cx = boardLeft + (file - 1) * cell + cell / 2;\n      const cy = boardTop + (8 - rank) * cell + cell / 2;\n      const isWhite = code === code.toUpperCase();\n      const glyph = isWhite ? WHITE_GLYPH[code] : BLACK_GLYPH[code];\n      ctx.strokeStyle = isWhite ? BLACK_PIECE : WHITE_PIECE;\n      ctx.fillStyle = isWhite ? WHITE_PIECE : BLACK_PIECE;\n      ctx.strokeText(glyph, cx, cy);\n      ctx.fillText(glyph, cx, cy);\n    }\n\n    ctx.restore();\n    window.__anyplotReady = true;\n  },\n};\nChart.register(chessBoardPlugin);\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: { datasets: [{ data: [] }] },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: 24 },\n    plugins: {\n      title: {\n        display: true,\n        text: \"Fool's Mate · chessboard-pieces · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22, weight: \"500\" },\n        padding: { bottom: 20 },\n      },\n      legend: { display: false },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      x: { display: false, min: 0, max: 1 },\n      y: { display: false, min: 0, max: 1 },\n    },\n  },\n});\n"}