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,
};