{"spec_id":"sudoku-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// sudoku-basic: Basic Sudoku Grid\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 88/100 | Created: 2026-06-25\n\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// Classic sudoku puzzle\nconst grid = [\n  [5, 3, 0, 0, 7, 0, 0, 0, 0],\n  [6, 0, 0, 1, 9, 5, 0, 0, 0],\n  [0, 9, 8, 0, 0, 0, 0, 6, 0],\n  [8, 0, 0, 0, 6, 0, 0, 0, 3],\n  [4, 0, 0, 8, 0, 3, 0, 0, 1],\n  [7, 0, 0, 0, 2, 0, 0, 0, 6],\n  [0, 6, 0, 0, 0, 0, 2, 8, 0],\n  [0, 0, 0, 4, 1, 9, 0, 0, 5],\n  [0, 0, 0, 0, 8, 0, 0, 7, 9],\n];\n\n// Layout: title area at top, centered grid below\nconst titleAreaH = 80;\nconst padSide = 75;\nconst padBottom = 60;\nconst availW = width - 2 * padSide;\nconst availH = height - titleAreaH - padBottom;\nconst cellSize = Math.floor(Math.min(availW, availH) / 9);\nconst gridSize = cellSize * 9;\nconst gridLeft = (width - gridSize) / 2;\nconst gridTop = titleAreaH + (availH - gridSize) / 2;\n\n// SVG mount\nconst svg = d3.select(\"#container\").append(\"svg\")\n  .attr(\"width\", width).attr(\"height\", height);\n\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${gridLeft},${gridTop})`);\n\n// Grid background\ng.append(\"rect\")\n  .attr(\"width\", gridSize).attr(\"height\", gridSize)\n  .attr(\"fill\", t.pageBg);\n\n// Alternating 3×3 box region fills — checkerboard subtle elevation\ng.selectAll(\"rect.box\")\n  .data(d3.range(9).filter(i => ((i % 3) + Math.floor(i / 3)) % 2 === 0))\n  .join(\"rect\")\n  .attr(\"class\", \"box\")\n  .attr(\"x\", i => (i % 3) * cellSize * 3)\n  .attr(\"y\", i => Math.floor(i / 3) * cellSize * 3)\n  .attr(\"width\", cellSize * 3)\n  .attr(\"height\", cellSize * 3)\n  .attr(\"fill\", t.elevatedBg)\n  .attr(\"opacity\", 0.5);\n\n// Vertical grid lines — D3 data-join over line indices\ng.selectAll(\"line.v-line\")\n  .data(d3.range(10))\n  .join(\"line\")\n  .attr(\"class\", \"v-line\")\n  .attr(\"x1\", i => i * cellSize).attr(\"y1\", 0)\n  .attr(\"x2\", i => i * cellSize).attr(\"y2\", gridSize)\n  .attr(\"stroke\", i => i % 3 === 0 ? t.ink : t.inkSoft)\n  .attr(\"stroke-width\", i => i % 3 === 0 ? 3.5 : 0.75)\n  .attr(\"stroke-linecap\", \"square\");\n\n// Horizontal grid lines — D3 data-join over line indices\ng.selectAll(\"line.h-line\")\n  .data(d3.range(10))\n  .join(\"line\")\n  .attr(\"class\", \"h-line\")\n  .attr(\"x1\", 0).attr(\"y1\", i => i * cellSize)\n  .attr(\"x2\", gridSize).attr(\"y2\", i => i * cellSize)\n  .attr(\"stroke\", i => i % 3 === 0 ? t.ink : t.inkSoft)\n  .attr(\"stroke-width\", i => i % 3 === 0 ? 3.5 : 0.75)\n  .attr(\"stroke-linecap\", \"square\");\n\n// Given numbers (clues) — D3 data-join over flat non-zero cells\nconst cells = grid.flatMap((row, ri) =>\n  row.map((v, ci) => ({ v, ri, ci }))\n).filter(d => d.v !== 0);\n\nconst fs = Math.round(cellSize * 0.52);\ng.selectAll(\"text.cell-num\")\n  .data(cells)\n  .join(\"text\")\n  .attr(\"class\", \"cell-num\")\n  .attr(\"x\", d => d.ci * cellSize + cellSize / 2)\n  .attr(\"y\", d => d.ri * cellSize + cellSize / 2)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"dominant-baseline\", \"central\")\n  .attr(\"fill\", t.palette[0])\n  .style(\"font-size\", `${fs}px`)\n  .style(\"font-weight\", \"700\")\n  .style(\"font-family\", \"Georgia, 'Times New Roman', serif\")\n  .text(d => d.v);\n\n// Title\nconst titleText = \"sudoku-basic · javascript · d3 · anyplot.ai\";\nconst n = titleText.length;\nconst titleSize = n > 67 ? Math.max(Math.round(22 * 67 / n), 14) : 22;\n\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", titleAreaH * 0.55)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"dominant-baseline\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", `${titleSize}px`)\n  .style(\"font-weight\", \"600\")\n  .text(titleText);\n"}