{"spec_id":"crossword-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// crossword-basic: Crossword Puzzle Grid\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-02\n//# anyplot-orientation: square\n\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport { ScatterChart } from \"@mui/x-charts/ScatterChart\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// The puzzle itself is a monochrome print artifact (per spec: \"Monochrome\n// design optimized for printing\") — its black/white pattern is the chart's\n// data, so it stays fixed across themes, like a categorical data color would.\n// Only the surrounding page and title (below) follow ANYPLOT_THEME.\nconst GRID_PAPER = \"#FAF8F1\";\nconst GRID_INK = \"#1A1A17\";\nconst GRID_LINE = \"#4A4A44\";\n\nconst GRID_SIZE = 15;\n\n// Black-cell coordinates for one half of the grid; mirrored below to enforce\n// the traditional 180-degree rotational symmetry of newspaper-style crosswords.\nconst HALF_BLOCKS = [\n  [0, 4], [0, 10],\n  [1, 4], [1, 10],\n  [2, 4], [2, 7], [2, 10],\n  [3, 0], [3, 1],\n  [4, 7],\n  [5, 3], [5, 11],\n  [6, 3], [6, 6], [6, 8], [6, 11],\n  [7, 0], [7, 5],\n];\n\nconst BLOCKED = new Set();\nHALF_BLOCKS.forEach(([r, c]) => {\n  BLOCKED.add(`${r},${c}`);\n  BLOCKED.add(`${GRID_SIZE - 1 - r},${GRID_SIZE - 1 - c}`);\n});\n\nconst isBlocked = (r, c) => BLOCKED.has(`${r},${c}`);\nconst isOpen = (r, c) =>\n  r >= 0 && r < GRID_SIZE && c >= 0 && c < GRID_SIZE && !isBlocked(r, c);\n\n// Number every cell that starts an across or down entry, in reading order —\n// the same rule newspaper puzzle constructors use.\nconst numbers = new Map();\nlet nextNumber = 1;\nfor (let r = 0; r < GRID_SIZE; r++) {\n  for (let c = 0; c < GRID_SIZE; c++) {\n    if (isBlocked(r, c)) continue;\n    const startsAcross = !isOpen(r, c - 1) && isOpen(r, c + 1);\n    const startsDown = !isOpen(r - 1, c) && isOpen(r + 1, c);\n    if (startsAcross || startsDown) {\n      numbers.set(`${r},${c}`, nextNumber++);\n    }\n  }\n}\n\nconst COLS = Array.from({ length: GRID_SIZE }, (_, c) => `c${c}`);\nconst ROWS = Array.from({ length: GRID_SIZE }, (_, r) => `r${r}`);\n\nconst cells = [];\nfor (let r = 0; r < GRID_SIZE; r++) {\n  for (let c = 0; c < GRID_SIZE; c++) {\n    cells.push({ id: `${r}-${c}`, x: COLS[c], y: ROWS[r], r, c });\n  }\n}\n\n// Every square renders in one custom marker: a black fill for blocked cells,\n// a paper-white fill with a clue number for cells that start a word.\nfunction GridMark(props) {\n  const { series, xScale, yScale } = props;\n  const cellW = xScale.bandwidth();\n  const cellH = yScale.bandwidth();\n\n  return (\n    <g>\n      {series.data.map((pt) => {\n        const cx = xScale(pt.x) ?? 0;\n        const cy = yScale(pt.y) ?? 0;\n        const blocked = isBlocked(pt.r, pt.c);\n        const number = numbers.get(`${pt.r},${pt.c}`);\n        return (\n          <g key={pt.id}>\n            <rect\n              x={cx}\n              y={cy}\n              width={cellW}\n              height={cellH}\n              fill={blocked ? GRID_INK : GRID_PAPER}\n              stroke={GRID_LINE}\n              strokeWidth={1.5}\n            />\n            {number !== undefined && (\n              <text\n                x={cx + cellW * 0.1}\n                y={cy + cellH * 0.32}\n                fontSize={cellW * 0.32}\n                fontWeight={600}\n                fill={GRID_INK}\n                textAnchor=\"start\"\n                dominantBaseline=\"middle\"\n              >\n                {number}\n              </text>\n            )}\n          </g>\n        );\n      })}\n      <rect\n        x={xScale(COLS[0]) ?? 0}\n        y={yScale(ROWS[0]) ?? 0}\n        width={cellW * GRID_SIZE}\n        height={cellH * GRID_SIZE}\n        fill=\"none\"\n        stroke={GRID_INK}\n        strokeWidth={3}\n      />\n    </g>\n  );\n}\n\nconst TITLE = \"crossword-basic · javascript · muix · anyplot.ai\";\nconst TITLE_HEIGHT = 64;\n\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const chartHeight = height - TITLE_HEIGHT;\n\n  // Keep every cell perfectly square: shrink the board to the smaller of the\n  // two available dimensions, then center it with even margins on both axes.\n  const MARGIN = 24;\n  const boardSize = Math.min(width, chartHeight) - MARGIN * 2;\n  const vMargin = (chartHeight - boardSize) / 2;\n  const hMargin = (width - boardSize) / 2;\n\n  return (\n    <Box sx={{ width, height, bgcolor: t.pageBg, display: \"flex\", flexDirection: \"column\" }}>\n      <Typography\n        sx={{\n          color: t.ink,\n          fontSize: 30,\n          fontWeight: 500,\n          textAlign: \"center\",\n          lineHeight: 1.2,\n          pt: \"16px\",\n          height: TITLE_HEIGHT,\n          fontFamily: \"inherit\",\n        }}\n      >\n        {TITLE}\n      </Typography>\n      <Box sx={{ flex: 1, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n        <ScatterChart\n          width={width}\n          height={chartHeight}\n          skipAnimation\n          disableVoronoi\n          series={[{ id: \"cells\", type: \"scatter\", data: cells, xAxisId: \"col\", yAxisId: \"row\" }]}\n          xAxis={[\n            {\n              id: \"col\",\n              scaleType: \"band\",\n              data: COLS,\n              categoryGapRatio: 0,\n              disableTicks: true,\n              disableLine: true,\n            },\n          ]}\n          yAxis={[\n            {\n              id: \"row\",\n              scaleType: \"band\",\n              data: ROWS,\n              categoryGapRatio: 0,\n              disableTicks: true,\n              disableLine: true,\n            },\n          ]}\n          topAxis={null}\n          bottomAxis={null}\n          leftAxis={null}\n          rightAxis={null}\n          margin={{ top: vMargin, bottom: vMargin, left: hMargin, right: hMargin }}\n          slots={{ scatter: GridMark }}\n          slotProps={{ legend: { hidden: true } }}\n        />\n      </Box>\n    </Box>\n  );\n}\n"}