{"_id":"@chessviewer-org/chess-viewer","_rev":"3-f04219163b1f315fd3cd461f3410f9bb","name":"@chessviewer-org/chess-viewer","dist-tags":{"latest":"1.2.0"},"versions":{"1.0.0":{"name":"@chessviewer-org/chess-viewer","version":"1.0.0","keywords":["chess","fen","fen-parser","fen-to-svg","svg","chess-svg","diagram","chess-diagram","board","chessboard","chessboard-image","chess-board","chess-position","chess-utils","board-themes","lichess","dpi","chessviewer","typescript"],"author":{"url":"https://chessvision.org","name":"ChessViewer","email":"contact@chessvision.org"},"license":"AGPL-3.0","_id":"@chessviewer-org/chess-viewer@1.0.0","maintainers":[{"name":"wisdomwebman","email":"wisdomwebman@proton.me"}],"homepage":"https://github.com/chessviewer-org/chess-viewer-utils#readme","bugs":{"url":"https://github.com/chessviewer-org/chess-viewer-utils/issues"},"dist":{"shasum":"d993221822b40984dc68b703d6ea6f881e55df16","tarball":"https://registry.npmjs.org/@chessviewer-org/chess-viewer/-/chess-viewer-1.0.0.tgz","fileCount":8,"integrity":"sha512-Y7b4sSZqZZliTLF/bh993XNLanztRJonLQ05c1niW/6Cx8jZQGYHDc+O9IGseD4kIwXNVn4mk0d11xVKs0cV8Q==","signatures":[{"sig":"MEYCIQCu6N0eXAwn5tKx7I/BGb/QfaWdAppTP3+BkzirxgQI5gIhANtYKoEuoES4zi6KhrGKko5gSblDl7XBE82l/DH/sOiP","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":175237},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"funding":{"url":"https://github.com/chessviewer-org/.github#support-the-project","type":"github"},"gitHead":"5721af10d9e802e32face0fa61a2d45fed5b70c6","scripts":{"dev":"tsup src/index.ts --format esm,cjs --dts --out-dir dist --watch","test":"npx tsx --test src/index.test.ts","build":"tsup src/index.ts --format esm,cjs --dts --out-dir dist","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"wisdomwebman","email":"wisdomwebman@proton.me"},"repository":{"url":"git+https://github.com/chessviewer-org/chess-viewer-utils.git","type":"git"},"_npmVersion":"11.12.1","description":"Chess diagram toolkit — parse & edit FEN positions, manipulate boards, render SVG diagrams, board themes, color, image & DPI utilities. Works in Node.js and browsers.","directories":{},"sideEffects":false,"_nodeVersion":"24.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","typescript":"^5.7.3","semantic-release":"^24.2.3","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^12.0.2","@semantic-release/github":"^11.0.6","@semantic-release/changelog":"^6.0.3","@semantic-release/commit-analyzer":"^13.0.1","@semantic-release/release-notes-generator":"^14.1.0","conventional-changelog-conventionalcommits":"^8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/chess-viewer_1.0.0_1783250686870_0.44384903836104495","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@chessviewer-org/chess-viewer","version":"1.1.0","keywords":["chess","fen","fen-parser","fen-to-svg","svg","chess-svg","diagram","chess-diagram","board","chessboard","chessboard-image","chess-board","chess-position","chess-utils","board-themes","lichess","dpi","chessviewer","typescript"],"author":{"url":"https://chessvision.org","name":"ChessViewer","email":"contact@chessvision.org"},"license":"AGPL-3.0","_id":"@chessviewer-org/chess-viewer@1.1.0","maintainers":[{"name":"wisdomwebman","email":"wisdomwebman@proton.me"}],"homepage":"https://github.com/chessviewer-org/chess-viewer-utils#readme","bugs":{"url":"https://github.com/chessviewer-org/chess-viewer-utils/issues"},"dist":{"shasum":"9fe73f02189528b823e4b040bfefab6c41d5760d","tarball":"https://registry.npmjs.org/@chessviewer-org/chess-viewer/-/chess-viewer-1.1.0.tgz","fileCount":8,"integrity":"sha512-1yMr8kxJr7jYBfrBQB9oFYctfxfpDOT/lNJHLXssKN8VYIJwh/vPiqCf7wmKR7AwkLBmJ+0HUJqjGY3/JQ3fmQ==","signatures":[{"sig":"MEUCIHK52mz6Fq5hfZMJAkDnZ+RrHH7shnybUJONlO6cugXgAiEA3jESw09lO6kNP1BOF+mXVee7rGoUUt6K89s9+qmwyYs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@chessviewer-org%2fchess-viewer@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":190849},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"funding":{"url":"https://github.com/chessviewer-org/.github#support-the-project","type":"github"},"gitHead":"863f0bc318ab0848f063ebeae0a4eeb96935a052","scripts":{"dev":"tsup src/index.ts --format esm,cjs --dts --out-dir dist --watch","test":"npx tsx --test src/index.test.ts","build":"tsup src/index.ts --format esm,cjs --dts --out-dir dist","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bcd52855-59d2-4100-b83b-7aa5daadcfff"}},"repository":{"url":"git+https://github.com/chessviewer-org/chess-viewer-utils.git","type":"git"},"_npmVersion":"11.18.0","description":"Chess diagram toolkit — parse & edit FEN positions, manipulate boards, render SVG diagrams, board themes, color, image & DPI utilities. Works in Node.js and browsers.","directories":{},"sideEffects":false,"_nodeVersion":"22.23.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","typescript":"^5.7.3","semantic-release":"^25.0.5","@semantic-release/git":"^10.0.1","@semantic-release/npm":"^13.1.5","@semantic-release/github":"^12.0.9","@semantic-release/changelog":"^6.0.3","@semantic-release/commit-analyzer":"^13.0.1","@semantic-release/release-notes-generator":"^14.1.0","conventional-changelog-conventionalcommits":"^10.2.1"},"_npmOperationalInternal":{"tmp":"tmp/chess-viewer_1.1.0_1783635498503_0.2232245497394516","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@chessviewer-org/chess-viewer","version":"1.2.0","description":"Chess diagram toolkit — parse & edit FEN positions, manipulate boards, render SVG diagrams, board themes, color, image & DPI utilities. Works in Node.js and browsers.","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"build":"tsup src/index.ts --format esm,cjs --dts --out-dir dist","dev":"tsup src/index.ts --format esm,cjs --dts --out-dir dist --watch","typecheck":"tsc --noEmit","test":"npx tsx --test src/*.test.ts","prepublishOnly":"npm run build && npm test"},"keywords":["chess","fen","fen-parser","fen-to-svg","svg","chess-svg","diagram","chess-diagram","board","chessboard","chessboard-image","chess-board","chess-position","chess-utils","board-themes","lichess","dpi","chessviewer","typescript"],"author":{"name":"ChessViewer","email":"contact@chessvision.org","url":"https://chessvision.org"},"license":"AGPL-3.0","funding":{"type":"github","url":"https://github.com/chessviewer-org/.github#support-the-project"},"repository":{"type":"git","url":"git+https://github.com/chessviewer-org/chess-viewer-utils.git"},"homepage":"https://github.com/chessviewer-org/chess-viewer-utils#readme","bugs":{"url":"https://github.com/chessviewer-org/chess-viewer-utils/issues"},"devDependencies":{"@semantic-release/changelog":"^6.0.3","@semantic-release/commit-analyzer":"^13.0.1","@semantic-release/git":"^10.0.1","@semantic-release/github":"^12.0.9","@semantic-release/npm":"^13.1.5","@semantic-release/release-notes-generator":"^14.1.0","conventional-changelog-conventionalcommits":"^10.2.1","semantic-release":"^25.0.5","tsup":"^8.3.0","typescript":"~5.9.3"},"engines":{"node":">=18"},"sideEffects":false,"publishConfig":{"access":"public"},"gitHead":"2d49ad077b5438c5819ac0c6b761b2079311625e","_id":"@chessviewer-org/chess-viewer@1.2.0","_nodeVersion":"22.23.1","_npmVersion":"11.18.0","dist":{"integrity":"sha512-RxJSeEFldznrthoaulnIPLUZ8aSDDSZsnPUaCqx7qjD2UFcTKtgIaaw2vP+OOiS6uYWqgd7Z3hLeq+1S3CJrKg==","shasum":"0d9305b187cad0af8fec854e02d70fc991f493b2","tarball":"https://registry.npmjs.org/@chessviewer-org/chess-viewer/-/chess-viewer-1.2.0.tgz","fileCount":8,"unpackedSize":209049,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@chessviewer-org%2fchess-viewer@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCc0CIvhpu+HjGvxsb1Ych/wjdZxxIPlsKIMOVoMKRIrwIhAKbHQMoCmMzykNQcOigQ+34JejAdb1KfUW7b/J75itBN"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bcd52855-59d2-4100-b83b-7aa5daadcfff"}},"directories":{},"maintainers":[{"name":"wisdomwebman","email":"wisdomwebman@proton.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/chess-viewer_1.2.0_1785575363282_0.9853506180174987"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-05T11:24:46.689Z","modified":"2026-08-01T09:09:23.764Z","1.0.0":"2026-07-05T11:24:47.011Z","1.1.0":"2026-07-09T22:18:18.650Z","1.2.0":"2026-08-01T09:09:23.461Z"},"bugs":{"url":"https://github.com/chessviewer-org/chess-viewer-utils/issues"},"author":{"name":"ChessViewer","email":"contact@chessvision.org","url":"https://chessvision.org"},"license":"AGPL-3.0","homepage":"https://github.com/chessviewer-org/chess-viewer-utils#readme","keywords":["chess","fen","fen-parser","fen-to-svg","svg","chess-svg","diagram","chess-diagram","board","chessboard","chessboard-image","chess-board","chess-position","chess-utils","board-themes","lichess","dpi","chessviewer","typescript"],"repository":{"type":"git","url":"git+https://github.com/chessviewer-org/chess-viewer-utils.git"},"description":"Chess diagram toolkit — parse & edit FEN positions, manipulate boards, render SVG diagrams, board themes, color, image & DPI utilities. Works in Node.js and browsers.","maintainers":[{"name":"wisdomwebman","email":"wisdomwebman@proton.me"}],"readme":"# @chessviewer-org/chess-viewer\n\nChess diagram generator and FEN utilities. Parse and edit FEN positions, render SVG board diagrams, manipulate boards, work with colors, board themes, presets, images, and history — in Node.js or the browser with **no dependencies and no DOM required**.\n\n[![npm version](https://img.shields.io/npm/v/@chessviewer-org/chess-viewer)](https://www.npmjs.com/package/@chessviewer-org/chess-viewer)\n[![npm downloads](https://img.shields.io/npm/dm/@chessviewer-org/chess-viewer)](https://www.npmjs.com/package/@chessviewer-org/chess-viewer)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/@chessviewer-org/chess-viewer)](https://bundlephobia.com/package/@chessviewer-org/chess-viewer)\n[![CI](https://github.com/chessviewer-org/chess-viewer-utils/actions/workflows/ci.yml/badge.svg)](https://github.com/chessviewer-org/chess-viewer-utils/actions/workflows/ci.yml)\n[![types](https://img.shields.io/npm/types/@chessviewer-org/chess-viewer)](https://www.npmjs.com/package/@chessviewer-org/chess-viewer)\n[![license](https://img.shields.io/npm/l/@chessviewer-org/chess-viewer)](LICENSE)\n\n- **Zero dependencies** — nothing pulled into your tree.\n- **Universal** — runs in Node.js and the browser, no DOM, no canvas, no network.\n- **Dual ESM + CJS** with first-class TypeScript types.\n- **Batteries included** — FEN parsing/editing, SVG rendering with an inline piece set, 20 board themes, color & DPI utilities, and more.\n\n---\n\n## Install\n\n```bash\nnpm install @chessviewer-org/chess-viewer\n# or\npnpm add @chessviewer-org/chess-viewer\n# or\nyarn add @chessviewer-org/chess-viewer\n```\n\n**Requirements:** Node.js ≥ 18, or any modern browser.\n\n---\n\n## Quick start\n\n```ts\nimport { generateDiagram, parseFEN, validateFEN } from '@chessviewer-org/chess-viewer';\n\n// Generate a self-contained SVG diagram\nconst svg = generateDiagram({\n  fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',\n  size: 400,\n  showCoords: true,\n  lightSquare: '#f0d9b5',\n  darkSquare: '#b58863',\n});\n\n// Write to a file (Node.js)\nimport { writeFileSync } from 'fs';\nwriteFileSync('board.svg', svg);\n\n// Or embed directly in HTML\ndocument.getElementById('board').innerHTML = svg;\n```\n\n### Example output\n\nThe diagram below is real SVG produced by `generateDiagram` — no images, no canvas:\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/chessviewer-org/chess-viewer-utils/main/assets/preview.svg\" width=\"360\" alt=\"Chess diagram rendered by chess-viewer (Italian Game, Wood theme)\">\n</p>\n\n---\n\n## Versioning\n\nThis package follows [Semantic Versioning](https://semver.org/):\n- **Patch** (`1.0.x`) — bug fixes, no API changes\n- **Minor** (`1.x.0`) — new exports added, fully backward-compatible\n- **Major** (`x.0.0`) — breaking API changes\n\n### Install a specific version\n\n```bash\n# Latest stable\nnpm install @chessviewer-org/chess-viewer\n\n# Specific version\nnpm install @chessviewer-org/chess-viewer@1.0.0\n\n# Latest minor of a major (e.g. 1.x)\nnpm install @chessviewer-org/chess-viewer@^1.0.0\n\n# Exact patch\nnpm install @chessviewer-org/chess-viewer@~1.0.0\n```\n\n### Check what version you have\n\n```bash\nnpm list @chessviewer-org/chess-viewer\n```\n\n### Check the latest version on npm\n\n```bash\nnpm view @chessviewer-org/chess-viewer version\n# or all published versions:\nnpm view @chessviewer-org/chess-viewer versions --json\n```\n\n### Upgrade to latest\n\n```bash\nnpm update @chessviewer-org/chess-viewer\n# or force latest:\nnpm install @chessviewer-org/chess-viewer@latest\n```\n\nSee [CHANGELOG.md](CHANGELOG.md) for what changed in each release.\n\n---\n\n## API Reference\n\n### `generateDiagram(options)`\n\nGenerates a self-contained SVG chess diagram. No DOM, no network requests.\n\n```ts\nimport { generateDiagram } from '@chessviewer-org/chess-viewer';\n\nconst svg = generateDiagram({\n  fen: 'rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1',\n  size: 480,            // board pixel size (default: 400)\n  showCoords: true,     // show a/b/c… and 1/2/3… labels (default: false)\n  coordStyle: 'inner',  // 'border' (default) or 'inner' — labels drawn inside the squares\n  flipped: false,       // show from Black's perspective (default: false)\n  showFrame: false,     // thin outer frame (default: false)\n  lightSquare: '#d4af7a',\n  darkSquare: '#8b4513',\n  coordColor: 'white',  // hex or 'white' | 'black' — keep coords legible on dark boards (default: '#000000')\n  label: 'Starting position after 1.e4',  // aria-label (default: 'Chess position')\n  annotations: {\n    highlights: [{ square: 'e4', style: 'fill' }, { square: 'e2', style: 'ring' }],\n    arrows: [{ from: 'e2', to: 'e4', color: '#15781b' }],\n    check: { square: 'e8', type: 'check' },\n  },\n});\n// → '<svg xmlns=\"http://www.w3.org/2000/svg\" …>…</svg>'\n```\n\n---\n\n### Board annotations\n\nDraw square highlights, move/threat arrows, and a check/checkmate glow on top\nof a diagram. Pass them straight to `generateDiagram` via `annotations`, or\nrender the SVG fragments yourself if you're composing your own board.\n\n```ts\nimport {\n  renderHighlightsSVG,\n  renderArrowsSVG,\n  renderCheckIndicatorSVG,\n  sanitizeAnnotations,\n} from '@chessviewer-org/chess-viewer';\n\n// bad squares, colors, or degenerate arrows are dropped, not thrown\nconst clean = sanitizeAnnotations({\n  highlights: [{ square: 'e4' }, { square: 'not-a-square' }],\n  arrows: [{ from: 'e2', to: 'e4' }],\n  check: { square: 'e1', type: 'check' },\n});\n\n// each render function needs a square → pixel mapping (same one the board\n// itself is drawn with, so everything lines up)\nconst toPixel = (square: string) => squareToPoint(square, { size: 400 });\nrenderHighlightsSVG(clean.highlights ?? [], toPixel, 50);\nrenderArrowsSVG(clean.arrows ?? [], toPixel, 50);\nrenderCheckIndicatorSVG(clean.check!, toPixel, 50);\n```\n\n---\n\n### Drag & drop / click-to-move\n\nPure helpers for building an interactive board on top of this library — hit\ntesting, move application, and a tap-to-move fallback for touch/accessibility.\n\n```ts\nimport {\n  parseFEN, STARTING_FEN,\n  pointToSquare, squareToPoint,\n  applyDragMove, applyDragRemove, applyPaletteDrop,\n  resolveClick,\n} from '@chessviewer-org/chess-viewer';\n\nconst board = parseFEN(STARTING_FEN);\n\n// what square is under this pointer position?\npointToSquare({ x: 210, y: 260 }, { size: 400 });  // → 'e3'\n\n// where should a square's ghost/preview element be drawn?\nsquareToPoint('e4', { size: 400 });  // → { x: 200, y: 200, size: 50 }\n\n// drop a dragged piece onto a square\nconst { board: next, moved, captured } = applyDragMove(board, 'e2', 'e4');\n\n// drag a piece off the board to delete it\napplyDragRemove(board, 'e2');\n\n// drop a piece from an outside palette/tray\napplyPaletteDrop(board, 'e4', 'Q');\n\n// tap-to-move: call on every square click with the current selection\nresolveClick('e4', 'e2', board);\n// → { kind: 'move', from: 'e2', to: 'e4' }\n```\n\n---\n\n### FEN utilities\n\n```ts\nimport {\n  parseFEN,\n  validateFEN,\n  validateFENDetailed,\n  getFENValidationError,\n  boardToFEN,\n  createEmptyBoard,\n  isBoardEmpty,\n  pieceToName,\n  describeBoardPosition,\n  FENParseError,\n} from '@chessviewer-org/chess-viewer';\n\n// Parse FEN → 8×8 matrix\nconst board = parseFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');\n// board[0][0] === 'r', board[7][4] === 'K'\n\n// Quick validity check\nvalidateFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR');  // → true\n\n// Human-readable error for UI\ngetFENValidationError('bad/fen');  // → 'Board must have 8 ranks'\n\n// Detailed error with user-facing messages\nconst result = validateFENDetailed('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');\n// → { isValid: true, errorMessage: null }\n\n// Matrix → FEN piece placement\nboardToFEN(board);  // → 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'\n\n// Screen-reader description of position\ndescribeBoardPosition(board);\n// → 'White: white king e1, white queen d1, … Black: black king e8, …'\n\n// FEN parse errors\ntry {\n  parseFEN('not-valid');\n} catch (e) {\n  if (e instanceof FENParseError) console.error(e.message);\n}\n```\n\n---\n\n### Full FEN record\n\nParse and serialize all six FEN fields — board, side to move, castling rights,\nen passant target, and move clocks. Placement-only strings parse too, filling\nstandard defaults.\n\n```ts\nimport {\n  parseFENRecord,\n  buildFENRecord,\n  toggleActiveColor,\n  fenPlacementField,\n  normalizeFEN,\n} from '@chessviewer-org/chess-viewer';\n\nconst record = parseFENRecord('rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 5 12');\n// → { board, activeColor: 'b', castling: 'KQkq', enPassant: 'e3', halfmove: 5, fullmove: 12 }\n\nbuildFENRecord(record);              // → 'rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 5 12'\ntoggleActiveColor(record).activeColor; // → 'w'  (pure, no mutation)\n\nfenPlacementField('rnbqkbnr/… w KQkq - 0 1'); // → 'rnbqkbnr/…'\nnormalizeFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR');\n// → 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1'\n```\n\n---\n\n### Board manipulation\n\nPure helpers for editing positions — every function returns a new board and\nnever mutates its input. Squares accept either algebraic strings (`'e4'`) or\n`[row, col]` matrix indices.\n\n```ts\nimport {\n  cloneBoard,\n  getPieceAt, setPieceAt, removePieceAt, movePiece,\n  flipBoard, listPieces, countPieces,\n  materialBalance, findKing, hasBothKings,\n} from '@chessviewer-org/chess-viewer';\n\nconst board = parseFEN('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR');\n\ngetPieceAt(board, 'e1');            // → 'K'\nconst next = movePiece(board, 'b1', 'c3');  // develops the knight, returns a new board\nremovePieceAt(next, 'd8');          // → new board with Black's queen removed\n\nflipBoard(board);                   // rotate 180° for Black's perspective\nlistPieces(board);                  // → [{ square: 'a8', piece: 'r' }, …]  (a8 → h1)\ncountPieces(board);                 // → { r: 2, n: 2, …, P: 8 }\nmaterialBalance(board);             // → 0  (positive = White ahead)\nfindKing(board, 'w');               // → 'e1'\nhasBothKings(board);                // → true  (exactly one king per side)\n```\n\n---\n\n### Theme & preset helpers\n\nLook up board themes, piece sets, and quality presets, with contrast-aware\nhelpers for legible coordinates.\n\n```ts\nimport {\n  getBoardTheme, listThemeIds,\n  getPieceSet, pieceSetsByPopularity,\n  getQualityPreset,\n  themeContrast, themeCoordinateColor,\n} from '@chessviewer-org/chess-viewer';\n\ngetBoardTheme('ocean');             // → { name: 'Ocean', light: '#c9e4f5', dark: '#4a90a4' }\nlistThemeIds();                     // → ['classic', 'brown', 'wood', …]\n\ngetPieceSet('cburnett');            // → { id: 'cburnett', name: 'Classic (CBurnett)' }\npieceSetsByPopularity()[0];         // → { id: 'cburnett', … }  (most popular first)\n\ngetQualityPreset(2);                // → { value: 2, label: 'Print 2× (600 DPI)', … }\n\nconst theme = getBoardTheme('classic')!;\nthemeContrast(theme);               // → WCAG contrast ratio between squares\nthemeCoordinateColor(theme);        // → 'white' | 'black'  (best on dark squares)\n```\n\n---\n\n### Image utilities\n\nRead raster dimensions straight from PNG/JPEG headers (no decoding, no DOM) and\ncompute physical print sizes — pairs naturally with `changeDPI`.\n\n```ts\nimport { readImageDimensions, physicalSize } from '@chessviewer-org/chess-viewer';\n\nconst bytes = new Uint8Array(await blob.arrayBuffer());\nreadImageDimensions(bytes);         // → { width: 1200, height: 1200 } | null\n\nphysicalSize(1200, 300);            // → { inches: 4, mm: 101.6 }\n```\n\n---\n\n### Board themes & constants\n\n```ts\nimport {\n  BOARD_THEMES,\n  PIECE_SETS,\n  PIECE_SET_POPULARITY,\n  QUALITY_PRESETS,\n  DEFAULT_LIGHT_SQUARE,\n  DEFAULT_DARK_SQUARE,\n  STARTING_FEN,\n  EMPTY_FEN,\n} from '@chessviewer-org/chess-viewer';\n\n// 20 built-in board color themes\nBOARD_THEMES.classic   // { name: 'Classic', light: '#f0d9b5', dark: '#b58863' }\nBOARD_THEMES.ocean     // { name: 'Ocean', light: '#c9e4f5', dark: '#4a90a4' }\n\n// 23 Lichess-compatible piece sets\nPIECE_SETS[0]  // { id: 'alpha', name: 'Alpha' }\n\n// Popularity-ranked piece set ids (most → least)\nPIECE_SET_POPULARITY[0]  // 'cburnett'\n\n// Print/social quality presets (DPI multipliers)\nQUALITY_PRESETS  // [{value:1, label:'Print 1× (300 DPI)', mode:'print', …}, …]\n\n// Convenient FEN constants\nSTARTING_FEN  // 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'\nEMPTY_FEN     // '8/8/8/8/8/8/8/8 w - - 0 1'\n```\n\n---\n\n### Color utilities\n\n```ts\nimport {\n  hexToRgb, rgbToHex,\n  rgbToHsv, hsvToRgb,\n  hexToHsv, hsvToHex,\n  relativeLuminance,\n  contrastRatio,\n  bestTextColor,\n} from '@chessviewer-org/chess-viewer';\n\nhexToRgb('#b58863')   // → { r: 181, g: 136, b: 99 }\nrgbToHex(181, 136, 99) // → '#b58863'\n\nconst { h, s, v } = hexToHsv('#b58863');\nhsvToHex(h, s, v)     // → '#b58863'\n\ncontrastRatio('#000000', '#ffffff')  // → 21\nbestTextColor('#b58863')             // → 'white' | 'black'\n```\n\n---\n\n### Coordinate utilities\n\n```ts\nimport {\n  squareToIndices,\n  indicesToSquare,\n  getSquareBounds,\n  isLightSquare,\n  getCoordinateParams,\n} from '@chessviewer-org/chess-viewer';\n\nsquareToIndices('e4')    // → [4, 4]  (row 0 = rank 8)\nindicesToSquare(4, 4)    // → 'e4'\nsquareToIndices('a8')    // → [0, 0]\nsquareToIndices('h1')    // → [7, 7]\n\nisLightSquare(0, 0)      // → true  (a8 is light)\nisLightSquare(7, 0)      // → false (a1 is dark)\n\n// Pixel bounds of a square (for canvas rendering)\ngetSquareBounds(0, 0, 50)  // → { x:0, y:0, width:50, height:50, centerX:25, centerY:25 }\n```\n\n---\n\n### History utilities\n\n```ts\nimport {\n  createHistoryEntry,\n  calculateStatus,\n  sortByMostRecent,\n  applyFilters,\n  mergeById,\n  convertToArchivedEntry,\n} from '@chessviewer-org/chess-viewer';\n\nconst entry = createHistoryEntry(\n  'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',\n  'manual'\n);\n// → { id: 1234567890, fen: '…', createdAt: …, lastActiveAt: …, source: 'manual', isFavorite: false }\n\ncalculateStatus(entry.lastActiveAt)  // → 'green' | 'yellow' | 'red'\n\n// Filter entries\napplyFilters(entries, { fenSearch: 'e4', favoritesOnly: false, source: 'manual' });\n\n// Merge two lists (e.g. cloud + local), primary wins on id collision\nmergeById(cloudEntries, localEntries);\n```\n\n---\n\n### Validation utilities\n\n```ts\nimport {\n  isValidHexColor,\n  sanitizeHexColor,\n  sanitizeFileName,\n  sanitizeInput,\n  safeJSONParse,\n} from '@chessviewer-org/chess-viewer';\n\nisValidHexColor('#f0d9b5')      // → true\nisValidHexColor('red')          // → false\n\nsanitizeHexColor('bad', '#fff') // → '#fff'\nsanitizeHexColor('#aabbcc')     // → '#aabbcc'\n\nsanitizeFileName('my<board>')   // → 'my-board-'\nsanitizeInput('<script>xss')    // → '&lt;script&gt;xss'\n\nsafeJSONParse('{\"a\":1}', {})   // → { a: 1 }\nsafeJSONParse('bad json', {})  // → {}  (fallback, no throw)\n```\n\n---\n\n### DPI encoding\n\n```ts\nimport { changeDPI } from '@chessviewer-org/chess-viewer';\n\n// Rewrite DPI metadata in a PNG or JPEG blob\nconst correctedBlob = await changeDPI(originalBlob, 300, 'png');\nconst correctedJpeg = await changeDPI(originalBlob, 150, 'jpeg');\n```\n\n---\n\n### Inline piece SVGs\n\n```ts\nimport { getPieceSVG, PIECES } from '@chessviewer-org/chess-viewer';\n\ngetPieceSVG('K')   // → '<svg …>…</svg>'  (white king, CBurnett style)\ngetPieceSVG('k')   // → '<svg …>…</svg>'  (black king)\ngetPieceSVG('X')   // → null\n\n// All 12 pieces\nObject.keys(PIECES)  // → ['wK','wQ','wR','wB','wN','wP','bK','bQ','bR','bB','bN','bP']\n```\n\n---\n\n## TypeScript\n\nThis package ships full TypeScript types. No `@types/…` package needed.\n\n```ts\nimport type {\n  PieceSymbol,\n  BoardMatrix,\n  DiagramOptions,\n  ValidationResult,\n  FENRecord,\n  ActiveColor,\n  SquareRef,\n  PiecePlacement,\n  ActiveHistoryEntry,\n  ArchivedHistoryEntry,\n  HistoryFilters,\n  BoardTheme,\n  QualityPreset,\n  PieceSet,\n  CoordinateParams,\n  SquareBounds,\n  ImageDimensions,\n  SquareHighlight,\n  Arrow,\n  CheckIndicator,\n  BoardAnnotations,\n  BoardPoint,\n  HitTestOptions,\n  DragMoveResult,\n  ClickResolution,\n} from '@chessviewer-org/chess-viewer';\n```\n\n---\n\n## Documentation & project\n\n- [Documentation](docs/README.md) — guides, architecture, and references.\n- [Roadmap](ROADMAP.md) — what's planned and what's out of scope.\n- [Changelog](CHANGELOG.md) — what changed in each release.\n- [Contributing](CONTRIBUTING.md) — how to propose and submit changes.\n- [Security policy](SECURITY.md) — how to report a vulnerability.\n- [Discussions](https://github.com/chessviewer-org/chess-viewer-utils/discussions) — questions and ideas.\n\n---\n\n## License\n\n[AGPL-3.0](LICENSE)\n","readmeFilename":"README.md"}