@xcpcio/board-app 0.83.2 → 0.85.3

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 (104) hide show
  1. package/dist/404.html +1 -1
  2. package/dist/assets/Balloon-B3cPN2yD.js +1 -0
  3. package/dist/assets/Board-BpIhDrix.css +1 -0
  4. package/dist/assets/Board-CWwqX2IB.js +190 -0
  5. package/dist/assets/ContestStateBadge-CQOaPmT8.js +1 -0
  6. package/dist/assets/ContestStateBadge-DgwznQrX.css +1 -0
  7. package/dist/assets/Countdown-BvWlPnYq.js +1 -0
  8. package/dist/assets/DataSourceInput-DCNrUj45.js +1 -0
  9. package/dist/assets/NavBar-O1kqnfE4.css +1 -0
  10. package/dist/assets/NavBar-t1eXyPKg.js +1 -0
  11. package/dist/assets/Resolver-CRBstFE3.css +1 -0
  12. package/dist/assets/Resolver-D_GRj6HP.js +1 -0
  13. package/dist/assets/RightArrowIcon-F6HBDgDW.js +1 -0
  14. package/dist/assets/TheInput-Btyuor_6.js +1 -0
  15. package/dist/assets/Tooltip-C9jfn33Z.css +1 -0
  16. package/dist/assets/Tooltip-DPDTUI-s.js +27 -0
  17. package/dist/assets/_..-D9qurBBH.css +1 -0
  18. package/dist/assets/_...all_-Chjz45WV.js +1 -0
  19. package/dist/assets/_...all_-TFChZhkr.js +6 -0
  20. package/dist/assets/_intlify-i18n-0-BmghNkij.js +1 -0
  21. package/dist/assets/_intlify-i18n-1-DzpDgiG5.js +1 -0
  22. package/dist/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
  23. package/dist/assets/balloon-BlTynoB_.js +1 -0
  24. package/dist/assets/board-A69GsfUB.js +1 -0
  25. package/dist/assets/board-layout-mk2Y30Yp.js +1 -0
  26. package/dist/assets/constant-YlOY-yXZ.js +1 -0
  27. package/dist/assets/countdown-DQRGLfdE.js +1 -0
  28. package/dist/assets/default-BNqFZJwJ.js +1 -0
  29. package/dist/assets/headless-BSf0YrvV.js +1 -0
  30. package/dist/assets/index-C1jdVImx.css +1 -0
  31. package/dist/assets/index-D57KOcpK.js +12 -0
  32. package/dist/assets/index-layout-Cg_FpvvD.js +1 -0
  33. package/dist/assets/pages-DZG1eNR4.css +1 -0
  34. package/dist/assets/{index-C0JhRtxz.js → pages-kJjvENga.js} +2 -2
  35. package/dist/assets/pagination-EIqoVe6M.js +21 -0
  36. package/dist/assets/person-DrN1htE6.js +1 -0
  37. package/dist/assets/query-CoPl3fiI.js +20 -0
  38. package/dist/assets/rank-Bjo0QJlM.js +1 -0
  39. package/dist/assets/rating-B3sXjXNN.js +1 -0
  40. package/dist/assets/rating-DiCQYzia.css +1 -0
  41. package/dist/assets/resolver-Db22e0q8.js +1 -0
  42. package/dist/assets/route-block-ChiJXsfH.js +1 -0
  43. package/dist/assets/utils-Cfm-h8Z9.js +1 -0
  44. package/dist/assets/vue.runtime.esm-bundler-DTMOfGWR.js +4 -0
  45. package/dist/index.html +1 -1
  46. package/package.json +45 -45
  47. package/src/auto-imports.d.ts +4 -4
  48. package/src/components/board/Board.vue +7 -4
  49. package/src/components/board/Export.vue +5 -1
  50. package/src/components/board/PendingPage.vue +85 -47
  51. package/src/components/board/Progress.vue +1 -0
  52. package/src/components/board/SeatMapView.vue +265 -0
  53. package/src/components/board/SubmissionsTable.vue +1 -2
  54. package/src/components/board/SubmitHeatMap.less +11 -3
  55. package/src/components/board/SubmitHeatMap.vue +56 -86
  56. package/src/components/board/TeamCard.vue +6 -6
  57. package/src/components/board/TeamOverview.vue +1 -0
  58. package/src/components/common/SegmentedControl.vue +87 -0
  59. package/src/components/flowbite/Tooltip.vue +79 -13
  60. package/src/components.d.ts +2 -1
  61. package/src/composables/useQueryBoardData.ts +18 -0
  62. package/dist/assets/Balloon.vue_vue_type_script_setup_true_lang-CZ_Wx4To.js +0 -1
  63. package/dist/assets/Board-B0CAQsJI.css +0 -1
  64. package/dist/assets/Board-BVDDayjC.js +0 -194
  65. package/dist/assets/ContestStateBadge-D6uf8wH-.css +0 -1
  66. package/dist/assets/ContestStateBadge-DV_spC5E.js +0 -1
  67. package/dist/assets/Countdown-BlEqPiY_.js +0 -1
  68. package/dist/assets/DataSourceInput.vue_vue_type_script_setup_true_lang-CwU_Xgr1.js +0 -1
  69. package/dist/assets/NavBar-DCPwz9qZ.js +0 -1
  70. package/dist/assets/NavBar-aT2xEvYz.css +0 -1
  71. package/dist/assets/Resolver-CY9BFOM6.js +0 -1
  72. package/dist/assets/Resolver-DV9FleNP.css +0 -1
  73. package/dist/assets/RightArrowIcon-CtYUhYc6.js +0 -1
  74. package/dist/assets/TheInput.vue_vue_type_script_setup_true_lang-DPzm-iVE.js +0 -1
  75. package/dist/assets/Tooltip-DzyPDwbo.css +0 -1
  76. package/dist/assets/Tooltip.vue_vue_type_script_setup_true_lang-DONjqLXY.js +0 -27
  77. package/dist/assets/_..-5I0UWqRG.css +0 -1
  78. package/dist/assets/_...all_-B7l1949F.js +0 -6
  79. package/dist/assets/_...all_-T0zBVu5S.js +0 -1
  80. package/dist/assets/_plugin-vue_export-helper-DlAUqK2U.js +0 -1
  81. package/dist/assets/board-layout-B5DfJEXx.js +0 -1
  82. package/dist/assets/constant-C2gH6ZII.js +0 -1
  83. package/dist/assets/dayjs-DjxrjdEm.js +0 -1
  84. package/dist/assets/default-B200Q7K-.js +0 -1
  85. package/dist/assets/en-BEousmNN.js +0 -1
  86. package/dist/assets/headless-OPBem1cy.js +0 -1
  87. package/dist/assets/index-B0gZ0sxU.js +0 -15
  88. package/dist/assets/index-BSgd4Lsj.js +0 -1
  89. package/dist/assets/index-Bk7pJUWz.js +0 -1
  90. package/dist/assets/index-Bojw84ll.css +0 -1
  91. package/dist/assets/index-D-lXJTYs.css +0 -1
  92. package/dist/assets/index-D-msPqye.css +0 -1
  93. package/dist/assets/index-DqbvaKi_.js +0 -1
  94. package/dist/assets/index-layout-DBiZ6YWC.js +0 -1
  95. package/dist/assets/index-uFy-gvEE.js +0 -1
  96. package/dist/assets/index-uV5YmJst.js +0 -1
  97. package/dist/assets/pagination-DQg-v0kh.js +0 -3
  98. package/dist/assets/person-EUz_T6aJ.js +0 -1
  99. package/dist/assets/rank-BUkksYCC.js +0 -1
  100. package/dist/assets/route-block-B_A1xBdJ.js +0 -1
  101. package/dist/assets/use-vmodel-Cl0DXIYt.js +0 -20
  102. package/dist/assets/zh-CN-DB4l8OJD.js +0 -1
  103. package/src/components/board/HeatMapTooltip.less +0 -115
  104. package/src/components/board/HeatMapTooltip.vue +0 -68
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { Rank, SelectOptionItem } from "@xcpcio/core";
3
3
  import type { Lang } from "@xcpcio/types";
4
+ import type { SegmentedControlOption } from "../common/SegmentedControl.vue";
4
5
 
5
6
  const props = defineProps<{
6
7
  rank: Rank;
@@ -11,6 +12,15 @@ const lang = computed(() => locale.value as unknown as Lang);
11
12
 
12
13
  const rank = computed(() => props.rank);
13
14
 
15
+ // Tab state - only show seat map tab if config exists
16
+ const hasSeatMap = computed(() => !!rank.value.contest.seatMap);
17
+ const currentTab = ref<"teams" | "seat-map">("teams");
18
+
19
+ const tabOptions = computed<SegmentedControlOption[]>(() => [
20
+ { value: "teams", label: t("pending_page.teams_tab") },
21
+ { value: "seat-map", label: t("pending_page.seat_map_tab") },
22
+ ]);
23
+
14
24
  const orgOptions = computed(() => {
15
25
  return rank.value.organizations.map((o) => {
16
26
  return {
@@ -99,72 +109,100 @@ const hasFilters = computed(() => {
99
109
  || rank.value.originTeams.length > 0
100
110
  || rank.value.contest.group.size > 0;
101
111
  });
112
+
113
+ // Compute filtered team IDs for seat map highlighting
114
+ const filteredTeamIds = computed(() => {
115
+ return new Set(filteredTeams.value.map(t => t.id));
116
+ });
102
117
  </script>
103
118
 
104
119
  <template>
105
120
  <div class="w-full">
106
121
  <div
107
- v-if="hasFilters"
122
+ v-if="hasFilters || hasSeatMap"
108
123
  flex="~ wrap gap-3 items-center"
109
- mb="4"
124
+ mb-6
110
125
  >
111
- <div
112
- v-if="groupOptions.length > 0"
113
- w-48
114
- >
115
- <TheMultiSelect
116
- :options="groupOptions"
117
- :selected-options="groupSelectedItems"
118
- :placeholder="t('standings.group')"
119
- @select="groupOnSelect"
120
- />
126
+ <!-- Filters on the left -->
127
+ <div flex="~ wrap gap-3 items-center" flex-1>
128
+ <div
129
+ v-if="groupOptions.length > 0"
130
+ w-48
131
+ >
132
+ <TheMultiSelect
133
+ :options="groupOptions"
134
+ :selected-options="groupSelectedItems"
135
+ :placeholder="t('standings.group')"
136
+ @select="groupOnSelect"
137
+ />
138
+ </div>
139
+
140
+ <div
141
+ v-if="rank.contest.options.enableOrganization && orgOptions.length > 0"
142
+ w-80
143
+ >
144
+ <TheMultiSelect
145
+ :options="orgOptions"
146
+ :selected-options="orgSelectedItems"
147
+ :placeholder="t('standings.organization')"
148
+ @select="orgOnSelect"
149
+ />
150
+ </div>
151
+
152
+ <div
153
+ v-if="teamsOptions.length > 0"
154
+ w-80
155
+ >
156
+ <TheMultiSelect
157
+ :options="teamsOptions"
158
+ :selected-options="teamsSelectedItems"
159
+ :placeholder="t('standings.team')"
160
+ @select="teamsOnSelect"
161
+ />
162
+ </div>
121
163
  </div>
122
164
 
165
+ <!-- Tab switcher on the right side -->
166
+ <SegmentedControl
167
+ v-if="hasSeatMap"
168
+ v-model="currentTab"
169
+ :options="tabOptions"
170
+ />
171
+ </div>
172
+
173
+ <!-- Teams Grid View -->
174
+ <template v-if="currentTab === 'teams'">
123
175
  <div
124
- v-if="rank.contest.options.enableOrganization && orgOptions.length > 0"
125
- w-116
176
+ v-if="hasTeams"
177
+ class="grid gap-4"
178
+ grid="~ cols-1 sm:cols-2 lg:cols-3 xl:cols-4"
126
179
  >
127
- <TheMultiSelect
128
- :options="orgOptions"
129
- :selected-options="orgSelectedItems"
130
- :placeholder="t('standings.organization')"
131
- @select="orgOnSelect"
180
+ <TeamCard
181
+ v-for="team in filteredTeams"
182
+ :key="team.id"
183
+ :rank="rank"
184
+ :team="team"
132
185
  />
133
186
  </div>
134
187
 
135
188
  <div
136
- v-if="teamsOptions.length > 0"
137
- w-116
189
+ v-else
190
+ flex="~ col items-center justify-center"
191
+ py="12"
192
+ text="gray-500 dark:gray-400"
138
193
  >
139
- <TheMultiSelect
140
- :options="teamsOptions"
141
- :selected-options="teamsSelectedItems"
142
- :placeholder="t('standings.team')"
143
- @select="teamsOnSelect"
144
- />
194
+ <span text="lg">{{ t("pending_page.no_teams") }}</span>
145
195
  </div>
146
- </div>
196
+ </template>
147
197
 
148
- <div
149
- v-if="hasTeams"
150
- class="grid gap-4"
151
- grid="~ cols-1 sm:cols-2 lg:cols-3 xl:cols-4"
152
- >
153
- <TeamCard
154
- v-for="team in filteredTeams"
155
- :key="team.id"
198
+ <!-- Seat Map View -->
199
+ <template v-else-if="currentTab === 'seat-map' && hasSeatMap">
200
+ <SeatMapView
156
201
  :rank="rank"
157
- :team="team"
202
+ :seat-map="rank.contest.seatMap!"
203
+ :filtered-team-ids="filteredTeamIds"
204
+ :all-teams="rank.originTeams"
158
205
  />
159
- </div>
160
-
161
- <div
162
- v-else
163
- flex="~ col items-center justify-center"
164
- py="12"
165
- text="gray-500 dark:gray-400"
166
- >
167
- <span text="lg">{{ t("pending_page.no_teams") }}</span>
168
- </div>
206
+ </template>
169
207
  </div>
170
208
  </template>
@@ -18,6 +18,7 @@ const rankOptions = computed({
18
18
  return props!.rankOptions;
19
19
  },
20
20
  set(value) {
21
+ // eslint-disable-next-line vue/custom-event-name-casing
21
22
  emit("update:rank-options", value);
22
23
  },
23
24
  });
@@ -0,0 +1,265 @@
1
+ <script setup lang="ts">
2
+ import type { Rank, SeatMap, SeatMapSection, Team } from "@xcpcio/core";
3
+ import type { Lang } from "@xcpcio/types";
4
+
5
+ const props = defineProps<{
6
+ rank: Rank;
7
+ seatMap: SeatMap;
8
+ filteredTeamIds: Set<string>;
9
+ allTeams: Team[];
10
+ }>();
11
+
12
+ const { locale } = useI18n();
13
+ const lang = computed(() => locale.value as unknown as Lang);
14
+
15
+ // === Seat Sizing ===
16
+ const SEAT_HEIGHT = 24;
17
+ const GAP_SIZE = 4;
18
+ const FONT_SIZE = 10;
19
+ const SEAT_PADDING = 8;
20
+
21
+ // Calculate max seat ID length in a section
22
+ function getMaxSeatIdLength(section: SeatMapSection): number {
23
+ let maxLen = 2; // minimum 2 characters
24
+ for (const row of section.grid) {
25
+ for (const seatId of row) {
26
+ if (seatId && seatId.length > maxLen) {
27
+ maxLen = seatId.length;
28
+ }
29
+ }
30
+ }
31
+ return maxLen;
32
+ }
33
+
34
+ // Calculate seat width based on max seat ID length
35
+ function getSeatWidth(section: SeatMapSection): number {
36
+ const maxLen = getMaxSeatIdLength(section);
37
+ const charWidth = FONT_SIZE * 0.6;
38
+ return Math.round(maxLen * charWidth + SEAT_PADDING);
39
+ }
40
+
41
+ // Build seat ID to team mapping
42
+ const seatToTeam = computed(() => {
43
+ return props.seatMap.buildSeatToTeamMap(props.allTeams);
44
+ });
45
+
46
+ // Get team for a given seat
47
+ function getTeamForSeat(seatId: string | null): Team | undefined {
48
+ if (!seatId) {
49
+ return undefined;
50
+ }
51
+ return seatToTeam.value.get(seatId);
52
+ }
53
+
54
+ // Get tooltip content for a seat
55
+ function getTooltipContent(seatId: string): string {
56
+ const team = getTeamForSeat(seatId);
57
+ if (!team) {
58
+ return seatId;
59
+ }
60
+
61
+ const name = team.name.getOrDefault(lang.value);
62
+ const org = team.organization?.name.getOrDefault(lang.value);
63
+ return org ? `${name} - ${org}` : name;
64
+ }
65
+
66
+ // Check if a seat's team should be highlighted
67
+ function isSeatHighlighted(seatId: string | null): boolean {
68
+ const team = getTeamForSeat(seatId);
69
+ if (!team) {
70
+ return false;
71
+ }
72
+ return props.filteredTeamIds.size === 0 || props.filteredTeamIds.has(team.id);
73
+ }
74
+
75
+ // Get max columns in the grid
76
+ function getMaxCols(section: SeatMapSection): number {
77
+ if (section.grid.length === 0) {
78
+ return 0;
79
+ }
80
+ return Math.max(...section.grid.map(row => row.length));
81
+ }
82
+
83
+ // Normalize row to have consistent column count
84
+ function getNormalizedRow(row: Array<string | null>, maxCols: number): Array<string | null> {
85
+ if (row.length >= maxCols) {
86
+ return row;
87
+ }
88
+ return [...row, ...Array.from({ length: maxCols - row.length }).fill(null) as Array<string | null>];
89
+ }
90
+
91
+ // Get grid style for a section
92
+ function getSectionGridStyle(section: SeatMapSection) {
93
+ const cols = getMaxCols(section);
94
+ const width = getSeatWidth(section);
95
+ return {
96
+ display: "grid",
97
+ gridTemplateColumns: `repeat(${cols}, ${width}px)`,
98
+ gap: `${GAP_SIZE}px`,
99
+ };
100
+ }
101
+
102
+ function getSeatStyle(section: SeatMapSection) {
103
+ return {
104
+ width: `${getSeatWidth(section)}px`,
105
+ height: `${SEAT_HEIGHT}px`,
106
+ fontSize: `${FONT_SIZE}px`,
107
+ };
108
+ }
109
+
110
+ function getRowLabelStyle() {
111
+ return {
112
+ height: `${SEAT_HEIGHT}px`,
113
+ marginBottom: `${GAP_SIZE}px`,
114
+ };
115
+ }
116
+ </script>
117
+
118
+ <template>
119
+ <div class="seat-map-container" flex="~ col items-center">
120
+ <div
121
+ v-for="(section, sectionIdx) in seatMap.sections"
122
+ :key="sectionIdx"
123
+ class="seat-map-section"
124
+ mb-8
125
+ >
126
+ <h3
127
+ v-if="section.title.getOrDefault(lang)"
128
+ text="lg center gray-700 dark:gray-300"
129
+ font="semibold"
130
+ mb-4
131
+ >
132
+ {{ section.title.getOrDefault(lang) }}
133
+ </h3>
134
+
135
+ <div
136
+ class="seat-grid-wrapper dark:bg-gray-800/40 dark:border-gray-700/50"
137
+ flex="~ justify-center"
138
+ bg-white
139
+ rounded-xl
140
+ p-4
141
+ border
142
+ border-gray-200
143
+ >
144
+ <!-- Row labels -->
145
+ <div
146
+ v-if="section.rowLabels.length > 0"
147
+ class="row-labels"
148
+ flex="~ col"
149
+ mr-3
150
+ >
151
+ <div
152
+ v-for="(label, rowIdx) in section.rowLabels"
153
+ :key="rowIdx"
154
+ :style="getRowLabelStyle()"
155
+ flex="~ items-center justify-center"
156
+ text="xs gray-400 dark:gray-500"
157
+ font-medium
158
+ w-6
159
+ >
160
+ {{ label ?? "" }}
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Seat grid -->
165
+ <div :style="getSectionGridStyle(section)">
166
+ <template v-for="(row, rowIdx) in section.grid" :key="rowIdx">
167
+ <template v-for="(seatId, colIdx) in getNormalizedRow(row, getMaxCols(section))" :key="`${rowIdx}-${colIdx}`">
168
+ <Tooltip v-if="seatId" placement="bottom">
169
+ <div
170
+ class="seat"
171
+ :class="{
172
+ 'seat-occupied': getTeamForSeat(seatId),
173
+ 'seat-highlighted': isSeatHighlighted(seatId),
174
+ 'seat-dimmed': filteredTeamIds.size > 0 && !isSeatHighlighted(seatId) && getTeamForSeat(seatId),
175
+ }"
176
+ :style="getSeatStyle(section)"
177
+ flex="~ items-center justify-center"
178
+ rounded="sm"
179
+ font="mono"
180
+ cursor-pointer
181
+ >
182
+ {{ seatId }}
183
+ </div>
184
+ <template #popper>
185
+ {{ getTooltipContent(seatId) }}
186
+ </template>
187
+ </Tooltip>
188
+ <div
189
+ v-else
190
+ :style="getSeatStyle(section)"
191
+ />
192
+ </template>
193
+ </template>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </template>
199
+
200
+ <style scoped>
201
+ /* Empty seat - flat gray */
202
+ .seat {
203
+ background-color: #f3f4f6;
204
+ border: 1px solid #d1d5db;
205
+ transition: all 0.2s ease;
206
+ }
207
+
208
+ .seat:hover {
209
+ background-color: #e5e7eb;
210
+ border-color: #9ca3af;
211
+ }
212
+
213
+ /* Occupied seat - flat blue */
214
+ .seat-occupied {
215
+ background-color: #bfdbfe;
216
+ border-color: #60a5fa;
217
+ color: #1e40af;
218
+ }
219
+
220
+ .seat-occupied:hover {
221
+ background-color: #93c5fd;
222
+ }
223
+
224
+ /* Highlighted seat - deep blue + slight scale */
225
+ .seat-highlighted {
226
+ background-color: #3b82f6;
227
+ border-color: #2563eb;
228
+ color: white;
229
+ transform: scale(1.05);
230
+ z-index: 1;
231
+ }
232
+
233
+ /* Dimmed state */
234
+ .seat-dimmed {
235
+ opacity: 0.35;
236
+ }
237
+
238
+ /* === Dark Mode === */
239
+ .dark .seat {
240
+ background-color: #374151;
241
+ border-color: #4b5563;
242
+ }
243
+
244
+ .dark .seat:hover {
245
+ background-color: #4b5563;
246
+ }
247
+
248
+ .dark .seat-occupied {
249
+ background-color: #1e3a5f;
250
+ border-color: #3b82f6;
251
+ color: #93c5fd;
252
+ }
253
+
254
+ .dark .seat-occupied:hover {
255
+ background-color: #1e4a7f;
256
+ }
257
+
258
+ /* Dark mode highlight - blue glow effect */
259
+ .dark .seat-highlighted {
260
+ background-color: #3b82f6;
261
+ border-color: #60a5fa;
262
+ color: white;
263
+ box-shadow: 0 0 16px rgba(59, 130, 246, 0.5);
264
+ }
265
+ </style>
@@ -150,8 +150,7 @@ const submissions = computed(() => {
150
150
  if (o.orgIds.length === 0
151
151
  && o.teamIds.length === 0
152
152
  && o.languages.length === 0
153
- && o.statuses.length === 0
154
- ) {
153
+ && o.statuses.length === 0) {
155
154
  return true;
156
155
  }
157
156
 
@@ -1,13 +1,21 @@
1
1
  @correct-colors: #9be9a8, #40c463, #30a14e, #216e39;
2
2
  @incorrect-colors: #ff8c8c, #ff4d4f, #ff1a1a, #d40d0d;
3
+ @correct-colors-dark: #216e39, #30a14e, #40c463, #9be9a8;
4
+ @incorrect-colors-dark: #d40d0d, #ff1a1a, #ff4d4f, #ff8c8c;
3
5
 
4
- .generate-heat-rules(@class, @colors) {
6
+ .generate-heat-rules(@class, @colors, @dark-colors) {
5
7
  each(@colors, {
6
8
  .@{class}[data-level="@{index}"] {
7
9
  background-color: @value;
8
10
  }
9
11
  });
12
+
13
+ each(@dark-colors, {
14
+ .dark .@{class}[data-level="@{index}"] {
15
+ background-color: @value;
16
+ }
17
+ });
10
18
  }
11
19
 
12
- .generate-heat-rules(correct-heat, @correct-colors);
13
- .generate-heat-rules(incorrect-heat, @incorrect-colors);
20
+ .generate-heat-rules(correct-heat, @correct-colors, @correct-colors-dark);
21
+ .generate-heat-rules(incorrect-heat, @incorrect-colors, @incorrect-colors-dark);