Spaces:
Running
Running
File size: 6,731 Bytes
e16348e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | /**
* theme.js: Board and piece theming module
*
* Owns the chess board's visual styling: square colours, rank/file label
* colours, last-move highlight colour, and the piece-set image path. Colours
* are read from the --board-* tokens in styles/tokens.css. The board uses
* Libre Brown and the piece set uses CBurnett.
* Exposes a small public API consumed by board.js and useExplorer.js.
*
* The CSS-generation function below targets chessground's DOM/class model
* (`cg-board`, `coords.ranks`/`coords.files`) - see the comment above
* _coordLabelCss() for how coordinate-label positioning and colouring are
* derived against that model.
*
* Public API:
* Theme.init(reconstructBoard) - register board reconstruction callback
* Theme.apply() - inject the board's theme <style> tag
* Theme.currentPieceSet() - return the active piece set id string
* Theme.pieceThemeFn() - return piece-code → image-path function
* (used by board.js's spare trays and
* promotion dialog)
*/
function _token(name) {
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
}
function _boardColors() {
const light = _token('--board-light');
const dark = _token('--board-dark');
return {
id: 'libre-brown',
lightSquare: light,
darkSquare: dark,
labelLight: dark, // label colour on light-square corners
labelDark: light, // label colour on dark-square corners
highlight: _token('--board-highlight'),
};
}
const PIECE_SET = 'cburnett';
let _reconstructBoard = null; // callback registered via init()
// ── Private: CSS generation ───────────────────────────────────────────────
//
// Chessground has no per-square light/dark CSS classes - the checkerboard
// pattern is a single background-image on `cg-board`. It is rendered here
// as a tiny tiled SVG data URI (_checkerboardDataUri). Rank/file labels
// are chessground's own `coords.ranks`/`coords.files` elements, positioned
// by static CSS in chess/board.css to sit inset at the board's corners
// rather than chessground's default outside-the-board placement. Colour is
// done with :nth-child selectors scoped by orientation, not the
// `.coord-light`/`.coord-dark` classes renderCoords() (wrap.ts) puts on
// each label: those classes don't correspond to the actual square each
// label overlays once repositioned this way (chessground's own official
// theme CSS doesn't use them for coords.ranks/coords.files either, for the
// same reason: they're only meaningful for chessground's default
// outside-the-board placement). See the matching comment in
// chess/board.css for the full derivation.
function _checkerboardDataUri(light, dark) {
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2'>
<rect width='1' height='1' fill='${light}'/>
<rect x='1' width='1' height='1' fill='${dark}'/>
<rect y='1' width='1' height='1' fill='${dark}'/>
<rect x='1' y='1' width='1' height='1' fill='${light}'/>
</svg>`;
return `data:image/svg+xml;base64,${btoa(svg)}`;
}
// A light-square label gets c.labelLight, a dark-square label gets
// c.labelDark. class + type + type + :nth-child, matched on all four
// orientation/parity combinations, carries the specificity needed to
// override chess/board.css's own default coord-label rule (see the comment there
// for why .coord-light/.coord-dark aren't used) regardless of source
// order. For white orientation the odd nth-child positions land on the
// a-file's dark squares (a1/a3/a5/a7, per chess/board.css's comment) and so take
// c.labelDark; the even positions (a2/a4/a6/a8, light squares) take
// c.labelLight. No text-shadow: the label colour itself carries enough
// contrast against its own square.
function _coordLabelCss(c) {
return `.orientation-white coords.ranks coord:nth-child(odd),
.orientation-white coords.files coord:nth-child(odd) { color: ${c.labelDark} !important; }
.orientation-white coords.ranks coord:nth-child(even),
.orientation-white coords.files coord:nth-child(even) { color: ${c.labelLight} !important; }
.orientation-black coords.ranks coord:nth-child(odd),
.orientation-black coords.files coord:nth-child(odd) { color: ${c.labelLight} !important; }
.orientation-black coords.ranks coord:nth-child(even),
.orientation-black coords.files coord:nth-child(even) { color: ${c.labelDark} !important; }`;
}
function _cssForBoard(c) {
return `/* chesstb-theme: ${c.id} */
cg-board {
background-image: url("${_checkerboardDataUri(c.lightSquare, c.darkSquare)}");
/* _checkerboardDataUri() returns a 2x2 tile (2 squares per axis).
25% 25% sizes it to 2/8 of the board per axis, so the tile repeats
4x4 (background-repeat's default) to fill all 64 squares; matches
the base checkerboard sizing in chess/board.css. */
background-size: 25% 25%;
}
${_coordLabelCss(c)}
cg-board square.last-move { background-color: ${c.highlight} !important; }`;
}
function _applyCSS() {
let tag = document.getElementById('chesstb-theme');
if (!tag) {
tag = document.createElement('style');
tag.id = 'chesstb-theme';
document.head.appendChild(tag);
}
tag.textContent = `@layer app {\n${_cssForBoard(_boardColors())}\n}`;
}
// ── Public API ────────────────────────────────────────────────────────────
/**
* Register the board reconstruction callback from board.js.
* Must be called before Board.init().
*/
function init(reconstructBoard) {
_reconstructBoard = reconstructBoard;
}
/**
* Inject the board's theme <style> tag and populate the piece-set-dependent
* tray/promotion images.
*/
function apply() {
_applyCSS();
if (_reconstructBoard) _reconstructBoard();
}
/** Return the active piece set id string. */
function currentPieceSet() { return PIECE_SET; }
// Built once - pieceThemeFn() below always returns this same function,
// rather than allocating a fresh closure per call.
function _pieceThemePath(piece) {
return `/static/pieces/${PIECE_SET}/${piece}.svg`;
}
/**
* Return the piece-code → image-path function for the active piece set
* (e.g. 'wQ' -> '/static/pieces/cburnett/wQ.svg'). Called by board.js to
* populate the spare-tray and promotion-dialog images.
*/
function pieceThemeFn() {
return _pieceThemePath;
}
export const Theme = {
init, apply,
currentPieceSet,
pieceThemeFn,
};
|