@react-chess-tools/react-chess-game 0.5.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/index.cjs +775 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/{index.d.mts → index.d.cts} +118 -12
  5. package/dist/index.d.ts +264 -0
  6. package/dist/{index.mjs → index.js} +171 -38
  7. package/dist/index.js.map +1 -0
  8. package/package.json +18 -9
  9. package/src/components/ChessGame/Theme.stories.tsx +242 -0
  10. package/src/components/ChessGame/ThemePresets.stories.tsx +144 -0
  11. package/src/components/ChessGame/parts/Board.tsx +6 -4
  12. package/src/components/ChessGame/parts/Root.tsx +11 -1
  13. package/src/docs/Theming.mdx +281 -0
  14. package/src/hooks/useChessGame.ts +23 -7
  15. package/src/index.ts +19 -0
  16. package/src/theme/__tests__/context.test.tsx +75 -0
  17. package/src/theme/__tests__/defaults.test.ts +61 -0
  18. package/src/theme/__tests__/utils.test.ts +106 -0
  19. package/src/theme/context.tsx +37 -0
  20. package/src/theme/defaults.ts +22 -0
  21. package/src/theme/index.ts +36 -0
  22. package/src/theme/presets.ts +41 -0
  23. package/src/theme/types.ts +56 -0
  24. package/src/theme/utils.ts +47 -0
  25. package/src/utils/__tests__/board.test.ts +118 -0
  26. package/src/utils/board.ts +18 -9
  27. package/src/utils/chess.ts +25 -5
  28. package/coverage/clover.xml +0 -6
  29. package/coverage/coverage-final.json +0 -1
  30. package/coverage/lcov-report/base.css +0 -224
  31. package/coverage/lcov-report/block-navigation.js +0 -87
  32. package/coverage/lcov-report/favicon.png +0 -0
  33. package/coverage/lcov-report/index.html +0 -101
  34. package/coverage/lcov-report/prettify.css +0 -1
  35. package/coverage/lcov-report/prettify.js +0 -2
  36. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  37. package/coverage/lcov-report/sorter.js +0 -196
  38. package/coverage/lcov.info +0 -0
  39. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,41 @@
1
+ import type { ChessGameTheme } from "./types";
2
+
3
+ /**
4
+ * Lichess-inspired theme with green highlights
5
+ */
6
+ export const lichessTheme: ChessGameTheme = {
7
+ board: {
8
+ lightSquare: { backgroundColor: "#f0d9b5" },
9
+ darkSquare: { backgroundColor: "#b58863" },
10
+ },
11
+ state: {
12
+ lastMove: "rgba(155, 199, 0, 0.41)",
13
+ check: "rgba(255, 0, 0, 0.5)",
14
+ activeSquare: "rgba(20, 85, 30, 0.5)",
15
+ dropSquare: { backgroundColor: "rgba(20, 85, 30, 0.3)" },
16
+ },
17
+ indicators: {
18
+ move: "rgba(20, 85, 30, 0.3)",
19
+ capture: "rgba(20, 85, 30, 0.3)",
20
+ },
21
+ };
22
+
23
+ /**
24
+ * Chess.com-inspired theme with green board and yellow highlights
25
+ */
26
+ export const chessComTheme: ChessGameTheme = {
27
+ board: {
28
+ lightSquare: { backgroundColor: "#ebecd0" },
29
+ darkSquare: { backgroundColor: "#779556" },
30
+ },
31
+ state: {
32
+ lastMove: "rgba(255, 255, 0, 0.5)",
33
+ check: "rgba(255, 0, 0, 0.7)",
34
+ activeSquare: "rgba(255, 255, 0, 0.5)",
35
+ dropSquare: { backgroundColor: "rgba(255, 255, 0, 0.4)" },
36
+ },
37
+ indicators: {
38
+ move: "rgba(0, 0, 0, 0.1)",
39
+ capture: "rgba(0, 0, 0, 0.1)",
40
+ },
41
+ };
@@ -0,0 +1,56 @@
1
+ import { CSSProperties } from "react";
2
+
3
+ /**
4
+ * Board appearance configuration - colors for light and dark squares
5
+ */
6
+ export interface BoardTheme {
7
+ /** Style for light squares */
8
+ lightSquare: CSSProperties;
9
+ /** Style for dark squares */
10
+ darkSquare: CSSProperties;
11
+ }
12
+
13
+ /**
14
+ * Game state highlight colors (RGBA color strings)
15
+ */
16
+ export interface StateTheme {
17
+ /** Background color for last move from/to squares */
18
+ lastMove: string;
19
+ /** Background color for king when in check */
20
+ check: string;
21
+ /** Background color for currently selected piece's square */
22
+ activeSquare: string;
23
+ /** Full CSSProperties for valid drop target squares */
24
+ dropSquare: CSSProperties;
25
+ }
26
+
27
+ /**
28
+ * Move indicator styling - colors for move dots and capture rings
29
+ */
30
+ export interface IndicatorTheme {
31
+ /** Color for move dots on empty destination squares (used in radial-gradient) */
32
+ move: string;
33
+ /** Color for capture rings on capturable pieces (used in radial-gradient) */
34
+ capture: string;
35
+ }
36
+
37
+ /**
38
+ * Complete theme configuration for ChessGame component
39
+ */
40
+ export interface ChessGameTheme {
41
+ board: BoardTheme;
42
+ state: StateTheme;
43
+ indicators: IndicatorTheme;
44
+ }
45
+
46
+ /**
47
+ * Utility type for creating partial versions of nested objects
48
+ */
49
+ export type DeepPartial<T> = {
50
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
51
+ };
52
+
53
+ /**
54
+ * Partial theme for user customization - allows overriding only specific properties
55
+ */
56
+ export type PartialChessGameTheme = DeepPartial<ChessGameTheme>;
@@ -0,0 +1,47 @@
1
+ import { merge } from "lodash";
2
+ import type { ChessGameTheme, PartialChessGameTheme } from "./types";
3
+ import { defaultGameTheme } from "./defaults";
4
+
5
+ /**
6
+ * Deep merges a partial theme with the default theme.
7
+ * Allows users to override only specific theme properties while keeping defaults for the rest.
8
+ *
9
+ * @param partialTheme - Partial theme with only the properties to override
10
+ * @returns Complete theme with overridden properties merged with defaults
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const customTheme = mergeTheme({
15
+ * state: { lastMove: "rgba(100, 200, 100, 0.6)" }
16
+ * });
17
+ * // Returns full theme with only lastMove color changed
18
+ * ```
19
+ */
20
+ export const mergeTheme = (
21
+ partialTheme?: PartialChessGameTheme,
22
+ ): ChessGameTheme => {
23
+ if (!partialTheme) {
24
+ return { ...defaultGameTheme };
25
+ }
26
+
27
+ return merge({}, defaultGameTheme, partialTheme);
28
+ };
29
+
30
+ /**
31
+ * Deep merges a partial theme with a base theme.
32
+ * Useful when extending an existing theme.
33
+ *
34
+ * @param baseTheme - The base theme to extend
35
+ * @param partialTheme - Partial theme with properties to override
36
+ * @returns Complete theme with overridden properties
37
+ */
38
+ export const mergeThemeWith = (
39
+ baseTheme: ChessGameTheme,
40
+ partialTheme?: PartialChessGameTheme,
41
+ ): ChessGameTheme => {
42
+ if (!partialTheme) {
43
+ return { ...baseTheme };
44
+ }
45
+
46
+ return merge({}, baseTheme, partialTheme);
47
+ };
@@ -1,6 +1,8 @@
1
1
  import { Chess } from "chess.js";
2
2
  import { getCustomSquareStyles, deepMergeChessboardOptions } from "../board";
3
3
  import { getGameInfo } from "../chess";
4
+ import { defaultGameTheme } from "../../theme/defaults";
5
+ import type { ChessGameTheme } from "../../theme/types";
4
6
 
5
7
  describe("Board Utilities", () => {
6
8
  describe("getCustomSquareStyles", () => {
@@ -139,6 +141,122 @@ describe("Board Utilities", () => {
139
141
  expect(styles.a3.background).toContain("25%");
140
142
  expect(styles.c3.background).toContain("25%");
141
143
  });
144
+
145
+ describe("with custom theme", () => {
146
+ const customTheme: ChessGameTheme = {
147
+ ...defaultGameTheme,
148
+ state: {
149
+ ...defaultGameTheme.state,
150
+ lastMove: "rgba(100, 200, 100, 0.6)",
151
+ check: "rgba(200, 50, 50, 0.7)",
152
+ activeSquare: "rgba(100, 100, 255, 0.5)",
153
+ },
154
+ indicators: {
155
+ move: "rgba(50, 50, 50, 0.2)",
156
+ capture: "rgba(200, 50, 50, 0.3)",
157
+ },
158
+ };
159
+
160
+ it("should use custom theme colors for last move", () => {
161
+ const game = new Chess();
162
+ game.move("e4");
163
+ const orientation = "w";
164
+ const info = getGameInfo(game, orientation);
165
+ const activeSquare = null;
166
+
167
+ const styles = getCustomSquareStyles(
168
+ game,
169
+ info,
170
+ activeSquare,
171
+ customTheme,
172
+ );
173
+
174
+ expect(styles.e2).toHaveProperty(
175
+ "backgroundColor",
176
+ "rgba(100, 200, 100, 0.6)",
177
+ );
178
+ expect(styles.e4).toHaveProperty(
179
+ "backgroundColor",
180
+ "rgba(100, 200, 100, 0.6)",
181
+ );
182
+ });
183
+
184
+ it("should use custom theme colors for active square", () => {
185
+ const game = new Chess();
186
+ const orientation = "w";
187
+ const info = getGameInfo(game, orientation);
188
+ const activeSquare = "e2";
189
+
190
+ const styles = getCustomSquareStyles(
191
+ game,
192
+ info,
193
+ activeSquare,
194
+ customTheme,
195
+ );
196
+
197
+ expect(styles.e2).toHaveProperty(
198
+ "backgroundColor",
199
+ "rgba(100, 100, 255, 0.5)",
200
+ );
201
+ });
202
+
203
+ it("should use custom theme colors for check", () => {
204
+ const game = new Chess(
205
+ "rnbqkbnr/ppp2ppp/8/3pp3/4P3/5Q2/PPPP1PPP/RNB1KBNR b KQkq - 1 3",
206
+ );
207
+ const orientation = "b";
208
+ const info = getGameInfo(game, orientation);
209
+ const modifiedInfo = { ...info, isCheck: true };
210
+ const activeSquare = null;
211
+
212
+ const styles = getCustomSquareStyles(
213
+ game,
214
+ modifiedInfo,
215
+ activeSquare,
216
+ customTheme,
217
+ );
218
+
219
+ expect(styles.e8).toHaveProperty(
220
+ "backgroundColor",
221
+ "rgba(200, 50, 50, 0.7)",
222
+ );
223
+ });
224
+
225
+ it("should use custom theme colors for move indicators", () => {
226
+ const game = new Chess();
227
+ const orientation = "w";
228
+ const info = getGameInfo(game, orientation);
229
+ const activeSquare = "e2";
230
+
231
+ const styles = getCustomSquareStyles(
232
+ game,
233
+ info,
234
+ activeSquare,
235
+ customTheme,
236
+ );
237
+
238
+ expect(styles.e3.background).toContain("rgba(50, 50, 50, 0.2)");
239
+ expect(styles.e4.background).toContain("rgba(50, 50, 50, 0.2)");
240
+ });
241
+
242
+ it("should use custom theme colors for capture indicators", () => {
243
+ const game = new Chess(
244
+ "rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2",
245
+ );
246
+ const orientation = "w";
247
+ const info = getGameInfo(game, orientation);
248
+ const activeSquare = "e4";
249
+
250
+ const styles = getCustomSquareStyles(
251
+ game,
252
+ info,
253
+ activeSquare,
254
+ customTheme,
255
+ );
256
+
257
+ expect(styles.d5.background).toContain("rgba(200, 50, 50, 0.3)");
258
+ });
259
+ });
142
260
  });
143
261
 
144
262
  describe("deepMergeChessboardOptions", () => {
@@ -3,14 +3,23 @@ import { type CSSProperties } from "react";
3
3
  import { merge } from "lodash";
4
4
  import type { ChessboardOptions } from "react-chessboard";
5
5
  import { getDestinationSquares, type GameInfo } from "./chess";
6
+ import type { ChessGameTheme } from "../theme/types";
7
+ import { defaultGameTheme } from "../theme/defaults";
6
8
 
7
- const LAST_MOVE_COLOR = "rgba(255, 255, 0, 0.5)";
8
- const CHECK_COLOR = "rgba(255, 0, 0, 0.5)";
9
-
9
+ /**
10
+ * Generates custom square styles based on game state and theme.
11
+ *
12
+ * @param game - Chess.js game instance
13
+ * @param info - Game info containing lastMove, isCheck, turn
14
+ * @param activeSquare - Currently selected square (if any)
15
+ * @param theme - Theme configuration (defaults to defaultGameTheme)
16
+ * @returns Record of square names to CSS properties
17
+ */
10
18
  export const getCustomSquareStyles = (
11
19
  game: Chess,
12
20
  info: GameInfo,
13
21
  activeSquare: Square | null,
22
+ theme: ChessGameTheme = defaultGameTheme,
14
23
  ) => {
15
24
  const customSquareStyles: Record<string, CSSProperties> = {};
16
25
 
@@ -18,16 +27,16 @@ export const getCustomSquareStyles = (
18
27
 
19
28
  if (lastMove) {
20
29
  customSquareStyles[lastMove.from] = {
21
- backgroundColor: LAST_MOVE_COLOR,
30
+ backgroundColor: theme.state.lastMove,
22
31
  };
23
32
  customSquareStyles[lastMove.to] = {
24
- backgroundColor: LAST_MOVE_COLOR,
33
+ backgroundColor: theme.state.lastMove,
25
34
  };
26
35
  }
27
36
 
28
37
  if (activeSquare) {
29
38
  customSquareStyles[activeSquare] = {
30
- backgroundColor: LAST_MOVE_COLOR,
39
+ backgroundColor: theme.state.activeSquare,
31
40
  };
32
41
  }
33
42
 
@@ -37,8 +46,8 @@ export const getCustomSquareStyles = (
37
46
  customSquareStyles[square] = {
38
47
  background:
39
48
  game.get(square) && game.get(square)?.color !== turn
40
- ? "radial-gradient(circle, rgba(1, 0, 0, 0.1) 85%, transparent 85%)"
41
- : "radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)",
49
+ ? `radial-gradient(circle, ${theme.indicators.capture} 85%, transparent 85%)`
50
+ : `radial-gradient(circle, ${theme.indicators.move} 25%, transparent 25%)`,
42
51
  };
43
52
  });
44
53
  }
@@ -48,7 +57,7 @@ export const getCustomSquareStyles = (
48
57
  return row.forEach((square) => {
49
58
  if (square?.type === "k" && square?.color === info.turn) {
50
59
  customSquareStyles[square.square] = {
51
- backgroundColor: CHECK_COLOR,
60
+ backgroundColor: theme.state.check,
52
61
  };
53
62
  }
54
63
  });
@@ -8,9 +8,17 @@ import _ from "lodash";
8
8
  * @returns A new Chess.js instance with the same state as the original.
9
9
  */
10
10
  export const cloneGame = (game: Chess) => {
11
- const copy = new Chess();
12
- copy.loadPgn(game.pgn());
13
- return copy;
11
+ try {
12
+ const copy = new Chess();
13
+ const pgn = game?.pgn();
14
+ if (pgn) {
15
+ copy.loadPgn(pgn);
16
+ }
17
+ return copy;
18
+ } catch (e) {
19
+ console.error("Failed to clone game:", e);
20
+ return new Chess();
21
+ }
14
22
  };
15
23
 
16
24
  /**
@@ -74,6 +82,10 @@ export const requiresPromotion = (
74
82
  game: Chess,
75
83
  move: Parameters<Chess["move"]>[0],
76
84
  ) => {
85
+ if (!game) {
86
+ throw new Error("Game is required");
87
+ }
88
+
77
89
  try {
78
90
  const copy = cloneGame(game);
79
91
  const result = copy.move(move);
@@ -100,13 +112,21 @@ export const getCurrentFen = (
100
112
  const tempGame = new Chess();
101
113
  if (currentMoveIndex === -1) {
102
114
  if (fen) {
103
- tempGame.load(fen);
115
+ try {
116
+ tempGame.load(fen);
117
+ } catch (e) {
118
+ console.error("Failed to load FEN in getCurrentFen:", fen, e);
119
+ }
104
120
  }
105
121
  } else {
106
122
  const moves = game.history().slice(0, currentMoveIndex + 1);
107
123
 
108
124
  if (fen) {
109
- tempGame.load(fen);
125
+ try {
126
+ tempGame.load(fen);
127
+ } catch (e) {
128
+ console.error("Failed to load FEN in getCurrentFen:", fen, e);
129
+ }
110
130
  }
111
131
  moves.forEach((move) => tempGame.move(move));
112
132
  }
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <coverage generated="1746218683707" clover="3.2.0">
3
- <project timestamp="1746218683707" name="All files">
4
- <metrics statements="0" coveredstatements="0" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0" elements="0" coveredelements="0" complexity="0" loc="0" ncloc="0" packages="0" files="0" classes="0"/>
5
- </project>
6
- </coverage>
@@ -1 +0,0 @@
1
- {}
@@ -1,224 +0,0 @@
1
- body, html {
2
- margin:0; padding: 0;
3
- height: 100%;
4
- }
5
- body {
6
- font-family: Helvetica Neue, Helvetica, Arial;
7
- font-size: 14px;
8
- color:#333;
9
- }
10
- .small { font-size: 12px; }
11
- *, *:after, *:before {
12
- -webkit-box-sizing:border-box;
13
- -moz-box-sizing:border-box;
14
- box-sizing:border-box;
15
- }
16
- h1 { font-size: 20px; margin: 0;}
17
- h2 { font-size: 14px; }
18
- pre {
19
- font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
20
- margin: 0;
21
- padding: 0;
22
- -moz-tab-size: 2;
23
- -o-tab-size: 2;
24
- tab-size: 2;
25
- }
26
- a { color:#0074D9; text-decoration:none; }
27
- a:hover { text-decoration:underline; }
28
- .strong { font-weight: bold; }
29
- .space-top1 { padding: 10px 0 0 0; }
30
- .pad2y { padding: 20px 0; }
31
- .pad1y { padding: 10px 0; }
32
- .pad2x { padding: 0 20px; }
33
- .pad2 { padding: 20px; }
34
- .pad1 { padding: 10px; }
35
- .space-left2 { padding-left:55px; }
36
- .space-right2 { padding-right:20px; }
37
- .center { text-align:center; }
38
- .clearfix { display:block; }
39
- .clearfix:after {
40
- content:'';
41
- display:block;
42
- height:0;
43
- clear:both;
44
- visibility:hidden;
45
- }
46
- .fl { float: left; }
47
- @media only screen and (max-width:640px) {
48
- .col3 { width:100%; max-width:100%; }
49
- .hide-mobile { display:none!important; }
50
- }
51
-
52
- .quiet {
53
- color: #7f7f7f;
54
- color: rgba(0,0,0,0.5);
55
- }
56
- .quiet a { opacity: 0.7; }
57
-
58
- .fraction {
59
- font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
60
- font-size: 10px;
61
- color: #555;
62
- background: #E8E8E8;
63
- padding: 4px 5px;
64
- border-radius: 3px;
65
- vertical-align: middle;
66
- }
67
-
68
- div.path a:link, div.path a:visited { color: #333; }
69
- table.coverage {
70
- border-collapse: collapse;
71
- margin: 10px 0 0 0;
72
- padding: 0;
73
- }
74
-
75
- table.coverage td {
76
- margin: 0;
77
- padding: 0;
78
- vertical-align: top;
79
- }
80
- table.coverage td.line-count {
81
- text-align: right;
82
- padding: 0 5px 0 20px;
83
- }
84
- table.coverage td.line-coverage {
85
- text-align: right;
86
- padding-right: 10px;
87
- min-width:20px;
88
- }
89
-
90
- table.coverage td span.cline-any {
91
- display: inline-block;
92
- padding: 0 5px;
93
- width: 100%;
94
- }
95
- .missing-if-branch {
96
- display: inline-block;
97
- margin-right: 5px;
98
- border-radius: 3px;
99
- position: relative;
100
- padding: 0 4px;
101
- background: #333;
102
- color: yellow;
103
- }
104
-
105
- .skip-if-branch {
106
- display: none;
107
- margin-right: 10px;
108
- position: relative;
109
- padding: 0 4px;
110
- background: #ccc;
111
- color: white;
112
- }
113
- .missing-if-branch .typ, .skip-if-branch .typ {
114
- color: inherit !important;
115
- }
116
- .coverage-summary {
117
- border-collapse: collapse;
118
- width: 100%;
119
- }
120
- .coverage-summary tr { border-bottom: 1px solid #bbb; }
121
- .keyline-all { border: 1px solid #ddd; }
122
- .coverage-summary td, .coverage-summary th { padding: 10px; }
123
- .coverage-summary tbody { border: 1px solid #bbb; }
124
- .coverage-summary td { border-right: 1px solid #bbb; }
125
- .coverage-summary td:last-child { border-right: none; }
126
- .coverage-summary th {
127
- text-align: left;
128
- font-weight: normal;
129
- white-space: nowrap;
130
- }
131
- .coverage-summary th.file { border-right: none !important; }
132
- .coverage-summary th.pct { }
133
- .coverage-summary th.pic,
134
- .coverage-summary th.abs,
135
- .coverage-summary td.pct,
136
- .coverage-summary td.abs { text-align: right; }
137
- .coverage-summary td.file { white-space: nowrap; }
138
- .coverage-summary td.pic { min-width: 120px !important; }
139
- .coverage-summary tfoot td { }
140
-
141
- .coverage-summary .sorter {
142
- height: 10px;
143
- width: 7px;
144
- display: inline-block;
145
- margin-left: 0.5em;
146
- background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
147
- }
148
- .coverage-summary .sorted .sorter {
149
- background-position: 0 -20px;
150
- }
151
- .coverage-summary .sorted-desc .sorter {
152
- background-position: 0 -10px;
153
- }
154
- .status-line { height: 10px; }
155
- /* yellow */
156
- .cbranch-no { background: yellow !important; color: #111; }
157
- /* dark red */
158
- .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
159
- .low .chart { border:1px solid #C21F39 }
160
- .highlighted,
161
- .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
162
- background: #C21F39 !important;
163
- }
164
- /* medium red */
165
- .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
166
- /* light red */
167
- .low, .cline-no { background:#FCE1E5 }
168
- /* light green */
169
- .high, .cline-yes { background:rgb(230,245,208) }
170
- /* medium green */
171
- .cstat-yes { background:rgb(161,215,106) }
172
- /* dark green */
173
- .status-line.high, .high .cover-fill { background:rgb(77,146,33) }
174
- .high .chart { border:1px solid rgb(77,146,33) }
175
- /* dark yellow (gold) */
176
- .status-line.medium, .medium .cover-fill { background: #f9cd0b; }
177
- .medium .chart { border:1px solid #f9cd0b; }
178
- /* light yellow */
179
- .medium { background: #fff4c2; }
180
-
181
- .cstat-skip { background: #ddd; color: #111; }
182
- .fstat-skip { background: #ddd; color: #111 !important; }
183
- .cbranch-skip { background: #ddd !important; color: #111; }
184
-
185
- span.cline-neutral { background: #eaeaea; }
186
-
187
- .coverage-summary td.empty {
188
- opacity: .5;
189
- padding-top: 4px;
190
- padding-bottom: 4px;
191
- line-height: 1;
192
- color: #888;
193
- }
194
-
195
- .cover-fill, .cover-empty {
196
- display:inline-block;
197
- height: 12px;
198
- }
199
- .chart {
200
- line-height: 0;
201
- }
202
- .cover-empty {
203
- background: white;
204
- }
205
- .cover-full {
206
- border-right: none !important;
207
- }
208
- pre.prettyprint {
209
- border: none !important;
210
- padding: 0 !important;
211
- margin: 0 !important;
212
- }
213
- .com { color: #999 !important; }
214
- .ignore-none { color: #999; font-weight: normal; }
215
-
216
- .wrapper {
217
- min-height: 100%;
218
- height: auto !important;
219
- height: 100%;
220
- margin: 0 auto -48px;
221
- }
222
- .footer, .push {
223
- height: 48px;
224
- }