{"spec_id":"chessboard-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// chessboard-basic: Chess Board Grid Visualization\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-01\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data --------------------------------------------------------------\n// 8x8 board, algebraic notation: files a-h (left to right), ranks 1-8\n// (bottom to top). h1 and a8 are light squares, matching standard\n// chess board orientation.\nconst files = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"];\nconst ranks = [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"];\nconst squares = [];\nfor (let row = 0; row < 8; row++) {\n  for (let col = 0; col < 8; col++) {\n    const file = files[col];\n    const rank = ranks[row];\n    squares.push({\n      file,\n      rank,\n      isDark: (col + row) % 2 === 0,\n      // h1 and a8 are the light-square corners the spec calls out —\n      // flagged here so we can give them a subtle focal accent below.\n      isNamedCorner: (file === \"h\" && rank === \"1\") || (file === \"a\" && rank === \"8\"),\n    });\n  }\n}\n\n// Board colors stay fixed across themes — like a physical board, not\n// chrome — only labels and the frame adapt to ANYPLOT_THEME.\nconst LIGHT_SQUARE = \"#EDE0C8\";\nconst DARK_SQUARE = \"#8B5A2B\";\n\n// --- Layout --------------------------------------------------------------\n// A bezel around the board (like a wooden surround under a physical board)\n// carries the drop shadow, so the frame itself stays a crisp, classic square.\nconst margin = { top: 100, right: 50, bottom: 70, left: 70 };\nconst bezelPad = 26;\nconst availW = width - margin.left - margin.right;\nconst availH = height - margin.top - margin.bottom;\nconst boardSize = Math.min(availW, availH) - bezelPad * 2;\nconst squareSize = boardSize / 8;\nconst footprint = boardSize + bezelPad * 2;\nconst boardLeft = margin.left + (availW - footprint) / 2 + bezelPad;\nconst boardTop = margin.top + (availH - footprint) / 2 + bezelPad;\n\n// --- Scales -------------------------------------------------------------\n// Idiomatic d3 band scales drive square + label placement (rank domain is\n// reversed so rank 8 lands at the top), rather than manual index math.\nconst x = d3.scaleBand().domain(files).range([0, boardSize]);\nconst y = d3.scaleBand().domain([...ranks].reverse()).range([0, boardSize]);\n\n// --- SVG mount -------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\n\n// Soft drop shadow under the board bezel — flood color rides the theme ink\n// token, so it reads as a shadow in light mode and a faint glow in dark mode.\nconst filter = svg.append(\"defs\").append(\"filter\").attr(\"id\", \"board-shadow\").attr(\"x\", \"-40%\").attr(\"y\", \"-40%\").attr(\"width\", \"180%\").attr(\"height\", \"180%\");\nfilter.append(\"feDropShadow\").attr(\"dx\", 0).attr(\"dy\", 10).attr(\"stdDeviation\", 14).attr(\"flood-color\", t.ink).attr(\"flood-opacity\", 0.25);\n\n// --- Bezel (elevated surface the board rests on) ----------------------------\nsvg\n  .append(\"rect\")\n  .attr(\"x\", boardLeft - bezelPad)\n  .attr(\"y\", boardTop - bezelPad)\n  .attr(\"width\", footprint)\n  .attr(\"height\", footprint)\n  .attr(\"rx\", 18)\n  .attr(\"fill\", t.elevatedBg)\n  .attr(\"filter\", \"url(#board-shadow)\");\n\nconst board = svg.append(\"g\").attr(\"transform\", `translate(${boardLeft},${boardTop})`);\n\n// --- Squares -----------------------------------------------------------------\nboard\n  .selectAll(\"rect\")\n  .data(squares)\n  .join(\"rect\")\n  .attr(\"x\", (d) => x(d.file))\n  .attr(\"y\", (d) => y(d.rank))\n  .attr(\"width\", x.bandwidth())\n  .attr(\"height\", y.bandwidth())\n  .attr(\"fill\", (d) => (d.isDark ? DARK_SQUARE : LIGHT_SQUARE));\n\n// --- Corner accent -------------------------------------------------------\n// A subtle amber dot marks h1 and a8, the light-square corners the spec\n// calls out — a light storytelling touch without disturbing the classic\n// board look.\nboard\n  .selectAll(\".corner-accent\")\n  .data(squares.filter((d) => d.isNamedCorner))\n  .join(\"circle\")\n  .attr(\"class\", \"corner-accent\")\n  .attr(\"cx\", (d) => x(d.file) + x.bandwidth() / 2)\n  .attr(\"cy\", (d) => y(d.rank) + y.bandwidth() / 2)\n  .attr(\"r\", squareSize * 0.09)\n  .attr(\"fill\", t.amber)\n  .attr(\"opacity\", 0.6);\n\n// --- Board frame -------------------------------------------------------------\nboard\n  .append(\"rect\")\n  .attr(\"x\", 0)\n  .attr(\"y\", 0)\n  .attr(\"width\", boardSize)\n  .attr(\"height\", boardSize)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.inkSoft)\n  .attr(\"stroke-width\", 2);\n\n// --- File labels (a-h, bottom) -------------------------------------------------\nboard\n  .selectAll(\".file-label\")\n  .data(files)\n  .join(\"text\")\n  .attr(\"class\", \"file-label\")\n  .attr(\"x\", (d) => x(d) + x.bandwidth() / 2)\n  .attr(\"y\", boardSize + 34)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"19px\")\n  .text((d) => d);\n\n// --- Rank labels (1-8, left) -------------------------------------------------\nboard\n  .selectAll(\".rank-label\")\n  .data(ranks)\n  .join(\"text\")\n  .attr(\"class\", \"rank-label\")\n  .attr(\"x\", -22)\n  .attr(\"y\", (d) => y(d) + y.bandwidth() / 2)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"dominant-baseline\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"19px\")\n  .text((d) => d);\n\n// --- Title -------------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 52)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"27px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"chessboard-basic · javascript · d3 · anyplot.ai\");\n"}