domma-cms 1.8.32 → 1.8.34
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.
- package/CHANGELOG.md +9 -0
- package/docs/DommaFeatures.md +13 -1
- package/package.json +1 -1
- package/plugins/_lib/admin/games/games.css +1 -1
- package/plugins/_lib/admin/games/kit.js +329 -124
- package/plugins/_lib/games/http.js +11 -1
- package/plugins/_lib/games/plays.js +28 -7
- package/plugins/crossword/CLAUDE.md +37 -0
- package/plugins/crossword/admin/css/index.css +18 -1
- package/plugins/crossword/admin/views/crossword.js +337 -205
- package/plugins/crossword/docs/guide.md +53 -0
- package/plugins/crossword/inject/body.html +1 -1
- package/plugins/crossword/plugin.js +38 -5
- package/plugins/crossword/plugin.json +4 -3
- package/plugins/crossword/public/crossword.css +36 -1
- package/plugins/crossword/public/crossword.js +468 -6
- package/plugins/docs/CLAUDE.md +318 -0
- package/plugins/docs/admin/css/docs.css +783 -0
- package/plugins/docs/admin/lib/api.js +340 -0
- package/plugins/docs/admin/lib/convert.js +482 -0
- package/plugins/docs/admin/lib/drafts.js +115 -0
- package/plugins/docs/admin/lib/editor.js +851 -0
- package/plugins/docs/admin/lib/find-replace.js +426 -0
- package/plugins/docs/admin/lib/folders.js +203 -0
- package/plugins/docs/admin/lib/panel-guard.js +73 -0
- package/plugins/docs/admin/lib/pdf-view.js +126 -0
- package/plugins/docs/admin/lib/send.js +156 -0
- package/plugins/docs/admin/lib/settings.js +142 -0
- package/plugins/docs/admin/lib/share.js +323 -0
- package/plugins/docs/admin/lib/shortcuts.js +44 -0
- package/plugins/docs/admin/lib/templates.js +194 -0
- package/plugins/docs/admin/lib/versions.js +192 -0
- package/plugins/docs/admin/templates/docs.html +92 -0
- package/plugins/docs/admin/templates/editor.html +86 -0
- package/plugins/docs/admin/views/docs.js +777 -0
- package/plugins/docs/admin/views/editor.js +978 -0
- package/plugins/docs/config.js +24 -0
- package/plugins/docs/docs/guide.md +270 -0
- package/plugins/docs/lib/extensions.js +213 -0
- package/plugins/docs/lib/importer.js +72 -0
- package/plugins/docs/lib/pdf-worker.js +48 -0
- package/plugins/docs/lib/pdf.js +222 -0
- package/plugins/docs/lib/store.js +748 -0
- package/plugins/docs/plugin.js +1108 -0
- package/plugins/docs/plugin.json +89 -0
- package/plugins/docs/plugin.public.js +114 -0
- package/plugins/docs/templates/public-doc.html +36 -0
- package/plugins/docs/tests/_env.js +10 -0
- package/plugins/docs/tests/_pdf.js +73 -0
- package/plugins/docs/tests/convert.test.js +95 -0
- package/plugins/docs/tests/drafts.test.js +82 -0
- package/plugins/docs/tests/pdf.test.js +86 -0
- package/plugins/docs/tests/plugin.test.js +816 -0
- package/plugins/docs/tests/shortcuts.test.js +30 -0
- package/plugins/free-tier.lock.json +147 -1
- package/plugins/garage/CLAUDE.md +51 -0
- package/plugins/garage/admin/css/garage.css +73 -0
- package/plugins/garage/admin/lib/garage.js +236 -0
- package/plugins/garage/admin/templates/garage.html +86 -0
- package/plugins/garage/admin/views/garage.js +581 -0
- package/plugins/garage/collections/garage-vehicles/schema.json +101 -0
- package/plugins/garage/config.js +18 -0
- package/plugins/garage/docs/guide.md +118 -0
- package/plugins/garage/plugin.js +471 -0
- package/plugins/garage/plugin.json +68 -0
- package/plugins/garage/tests/access.test.js +134 -0
- package/plugins/garage/tests/garage.test.js +106 -0
- package/plugins/messages/CLAUDE.md +230 -0
- package/plugins/messages/admin/css/messages.css +117 -0
- package/plugins/messages/admin/lib/messages.js +452 -0
- package/plugins/messages/admin/templates/messages.html +76 -0
- package/plugins/messages/admin/views/messages.js +1219 -0
- package/plugins/messages/collections/user-message-threads/schema.json +22 -0
- package/plugins/messages/collections/user-messages/schema.json +23 -0
- package/plugins/messages/config.js +14 -0
- package/plugins/messages/docs/guide.md +176 -0
- package/plugins/messages/lib/call.js +138 -0
- package/plugins/messages/plugin.js +994 -0
- package/plugins/messages/plugin.json +59 -0
- package/plugins/messages/plugin.public.js +52 -0
- package/plugins/messages/templates/call.html +6 -0
- package/plugins/messages/tests/after-send.test.js +22 -0
- package/plugins/messages/tests/api.test.js +187 -0
- package/plugins/messages/tests/call.test.js +497 -0
- package/plugins/messages/tests/drafts.test.js +25 -0
- package/plugins/messages/tests/messages.test.js +167 -0
- package/plugins/quiz/CLAUDE.md +43 -0
- package/plugins/quiz/admin/css/index.css +13 -1
- package/plugins/quiz/admin/views/quiz.js +423 -245
- package/plugins/quiz/docs/guide.md +61 -0
- package/plugins/quiz/inject/body.html +1 -1
- package/plugins/quiz/plugin.js +39 -5
- package/plugins/quiz/plugin.json +4 -3
- package/plugins/quiz/public/quiz.css +36 -1
- package/plugins/quiz/public/quiz.js +298 -6
- package/plugins/theme-roller/CLAUDE.md +65 -0
- package/plugins/theme-roller/admin/css/theme-roller.css +43 -0
- package/plugins/theme-roller/admin/lib/prompt.js +22 -0
- package/plugins/theme-roller/admin/lib/themes.js +196 -0
- package/plugins/theme-roller/admin/lib/view.js +88 -0
- package/plugins/theme-roller/admin/templates/theme-roller.html +73 -0
- package/plugins/theme-roller/admin/views/theme-roller-view.js +639 -0
- package/plugins/theme-roller/config.js +1 -0
- package/plugins/theme-roller/docs/guide.md +104 -0
- package/plugins/theme-roller/plugin.js +263 -0
- package/plugins/theme-roller/plugin.json +42 -0
- package/plugins/theme-roller/tests/api.test.js +161 -0
- package/plugins/theme-roller/tests/prompt-values.test.js +22 -0
- package/plugins/theme-roller/tests/themes.test.js +77 -0
- package/plugins/theme-roller/tests/view.test.js +55 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Guide
|
|
3
|
+
order: 1
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Put crosswords on your website. You type the answers and clues, the grid is laid out for you, and visitors race the clock to a leaderboard. Crossword is free.
|
|
7
|
+
|
|
8
|
+
## What it does
|
|
9
|
+
|
|
10
|
+
- **You write words and clues; it builds the grid.** The preview updates as you type. Not happy with the shape? **Another layout** tries a different arrangement of the same words. Any word that will not cross the others is listed so you can change it.
|
|
11
|
+
- **Phrases are fine.** "New York" shows as (3,4) and "well-being" as (4-5) in the clue.
|
|
12
|
+
- **Timing kept by the server**, from the moment the crossword is started to the moment it is solved.
|
|
13
|
+
- **Check** marks the wrong squares, at a time penalty you choose (30 seconds unless you change it). You can switch checking off.
|
|
14
|
+
- **Submitting a wrong grid** costs a smaller penalty (10 seconds unless you change it), so guessing is not free. Empty squares are pointed out and cost nothing.
|
|
15
|
+
- **Leaderboards** of the fastest solvers for today, this week, this month and all time, best go per name.
|
|
16
|
+
- The answers never reach the visitor's browser until they have solved it.
|
|
17
|
+
- Optional overall time limit, opening and closing times, and a limit on goes per name.
|
|
18
|
+
|
|
19
|
+
## Getting started
|
|
20
|
+
|
|
21
|
+
1. Switch Crossword on in **Tools**.
|
|
22
|
+
2. Open **Tools > Games > Crossword** and choose **New crossword**.
|
|
23
|
+
3. Add at least two answers with their clues. Ten to twenty words make a good grid.
|
|
24
|
+
4. Press **Test** and solve it yourself (see below).
|
|
25
|
+
5. Set the status to **Open** and save.
|
|
26
|
+
6. Put it on any page:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
[crossword id="sea-life" /]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Testing a crossword
|
|
33
|
+
|
|
34
|
+
**Test** (in the editor, on each crossword in the list, and on its Plays screen) opens the crossword right there in the admin, exactly as your players will see it: the same grid, clock, checks and penalties.
|
|
35
|
+
|
|
36
|
+
- It plays the version you last saved. With unsaved changes the button says **Save and test**, and saves first.
|
|
37
|
+
- It works whatever the status, so you can try a draft before anyone can see it.
|
|
38
|
+
- A test go is never saved to the leaderboard. It is not in the Plays list, it does not count towards the goes each name is allowed, and it does not raise a notification. When you solve it, it tells you where you would have come.
|
|
39
|
+
- Each new test clears away the one before.
|
|
40
|
+
|
|
41
|
+
## A leaderboard on its own
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
[crossword-leaderboard id="sea-life" period="all" limit="10" title="Fastest solvers" /]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Playing
|
|
48
|
+
|
|
49
|
+
Click a square, or a clue, and type. Typing moves along the word. Arrow keys move around, clicking the same square again or pressing Space turns between across and down, Tab jumps to the next clue, and Backspace clears. Letters are remembered if the page is reloaded.
|
|
50
|
+
|
|
51
|
+
## Looking after the board
|
|
52
|
+
|
|
53
|
+
Open a crossword's **Plays** to see every go (your own test goes are not shown). You can hide a play from the leaderboard, delete it, or reset the leaderboard.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<script type="module" src="/plugins/crossword/public/crossword.js?v=1.
|
|
1
|
+
<script type="module" src="/plugins/crossword/public/crossword.js?v=1.1.0"></script>
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* GET /puzzles every crossword as a list row
|
|
7
7
|
* GET /puzzles/:id · POST /puzzles · PUT /puzzles/:id · DELETE /puzzles/:id
|
|
8
8
|
* POST /puzzles/:id/copy a copy, as a draft
|
|
9
|
+
* GET /puzzles/:id/test what the player's start screen shows, as if it were open
|
|
10
|
+
* POST /puzzles/:id/test a TEST play of it as saved (draft or closed too) -> {playId, token, name, info};
|
|
11
|
+
* played through the /public/play routes, never on a board (see _lib/games/plays.js)
|
|
9
12
|
* GET /puzzles/:id/plays · DELETE /puzzles/:id/plays · PUT /plays/:id · DELETE /plays/:id
|
|
10
13
|
* The grid is laid out on the server from the words and the seed (the builder previews with
|
|
11
14
|
* the same function, public/lib/layout.js); a save answers `unplaced` for words that did not fit.
|
|
@@ -24,7 +27,7 @@
|
|
|
24
27
|
*/
|
|
25
28
|
|
|
26
29
|
import {cleanPlayerName} from '../_lib/games/names.js';
|
|
27
|
-
import {boardQuery, fail, isBot, limited, mountPlayAdmin, todayBadge, tokenOf} from '../_lib/games/http.js';
|
|
30
|
+
import {boardQuery, fail, isBot, limited, mountPlayAdmin, testerName, todayBadge, tokenOf} from '../_lib/games/http.js';
|
|
28
31
|
import {solutionGrid} from './public/lib/layout.js';
|
|
29
32
|
import {catchUp, cleanPuzzle, compare, finish, isOpen, publicPuzzle, readCells, summary, view} from './lib/crossword.js';
|
|
30
33
|
import {allPuzzles, ensureCollections, getPuzzle, plays, puzzleBySlug, removePuzzle, savePuzzle, uniqueSlug} from './lib/store.js';
|
|
@@ -83,7 +86,7 @@ export default async function crosswordPlugin(fastify, options) {
|
|
|
83
86
|
|
|
84
87
|
fastify.get('/puzzles', can('read'), async () => {
|
|
85
88
|
const everyPlay = await plays.all();
|
|
86
|
-
return {puzzles: (await allPuzzles()).map(p => summary(p, everyPlay.filter(x => x.gameId === p.id)))
|
|
89
|
+
return {puzzles: (await allPuzzles()).map(p => summary(p, everyPlay.filter(x => x.gameId === p.id && x.test !== true)))
|
|
87
90
|
.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)))};
|
|
88
91
|
});
|
|
89
92
|
|
|
@@ -119,6 +122,27 @@ export default async function crosswordPlugin(fastify, options) {
|
|
|
119
122
|
|
|
120
123
|
mountPlayAdmin(fastify, {store: plays, base: '/puzzles', can, exists: async (id) => Boolean(await getPuzzle(id))});
|
|
121
124
|
|
|
125
|
+
// The admin tries the crossword from the Tool: the saved grid, whatever its status or dates, through
|
|
126
|
+
// the same player routes as anyone. The play is marked `test`, so it never counts anywhere.
|
|
127
|
+
const testInfo = (puzzle) => ({...publicInfo(puzzle), open: true, reason: ''});
|
|
128
|
+
|
|
129
|
+
fastify.get('/puzzles/:id/test', can('manage'), async (request, reply) => {
|
|
130
|
+
const puzzle = await getPuzzle(request.params.id);
|
|
131
|
+
if (!puzzle) return fail(reply, 404, 'That crossword is not here any more.');
|
|
132
|
+
return {info: testInfo(puzzle)};
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
fastify.post('/puzzles/:id/test', can('manage'), async (request, reply) => {
|
|
136
|
+
const puzzle = await getPuzzle(request.params.id);
|
|
137
|
+
if (!puzzle) return fail(reply, 404, 'That crossword is not here any more.');
|
|
138
|
+
if (!puzzle.layout?.entries?.length) return fail(reply, 409, 'Add answers and clues and save, then test it.', {code: 'empty'});
|
|
139
|
+
await plays.clearTests(puzzle.id);
|
|
140
|
+
const name = testerName(request);
|
|
141
|
+
const play = await plays.start({gameId: puzzle.id, name, maxScore: 1, state: {checks: 0, submits: 0, penaltyMs: 0},
|
|
142
|
+
userId: String(request.user?.id || ''), test: true});
|
|
143
|
+
return reply.code(201).send({playId: play.id, token: play.token, name, info: testInfo(puzzle)});
|
|
144
|
+
});
|
|
145
|
+
|
|
122
146
|
// ---- Players ----------------------------------------------------------------
|
|
123
147
|
|
|
124
148
|
const shownPuzzle = async (slug) => {
|
|
@@ -155,18 +179,22 @@ export default async function crosswordPlugin(fastify, options) {
|
|
|
155
179
|
});
|
|
156
180
|
|
|
157
181
|
/** The play behind a request, up to date with the clock. Null after answering with the reason. */
|
|
182
|
+
/** The play went while this request was on its way (a reset, a delete, or the next test clearing it). */
|
|
183
|
+
const gone = (reply) => fail(reply, 404, 'That game is not here any more. Start again.', {code: 'gone'});
|
|
184
|
+
|
|
158
185
|
async function livePlay(request, reply) {
|
|
159
186
|
const opened = await plays.open(request.params.id, tokenOf(request));
|
|
160
187
|
if (!opened) { fail(reply, 404, 'That game is not here any more. Start again.', {code: 'gone'}); return null; }
|
|
161
188
|
const puzzle = await getPuzzle(opened.gameId);
|
|
162
189
|
if (!puzzle) { fail(reply, 404, 'That crossword is not here any more.', {code: 'gone'}); return null; }
|
|
163
190
|
const play = await plays.change(opened.id, (p) => (catchUp(puzzle, p) ? p : null));
|
|
191
|
+
if (!play) { gone(reply); return null; }
|
|
164
192
|
return {puzzle, play};
|
|
165
193
|
}
|
|
166
194
|
|
|
167
195
|
async function afterFinish(puzzle, play) {
|
|
168
196
|
try {
|
|
169
|
-
if (!puzzle.leaderboard || await plays.placeOf(play, 'all') !== 1) return;
|
|
197
|
+
if (play.test || !puzzle.leaderboard || await plays.placeOf(play, 'all') !== 1) return;
|
|
170
198
|
hooks.notify?.({
|
|
171
199
|
title: `Fastest solve of "${puzzle.title}"`,
|
|
172
200
|
body: `${play.name} solved it in ${secs(play.timeMs)} seconds`,
|
|
@@ -195,6 +223,7 @@ export default async function crosswordPlugin(fastify, options) {
|
|
|
195
223
|
p.state.penaltyMs = (p.state.penaltyMs || 0) + puzzle.checkPenalty * 1000;
|
|
196
224
|
return p;
|
|
197
225
|
});
|
|
226
|
+
if (!play) return gone(reply);
|
|
198
227
|
return {wrong, empty, play: view(puzzle, play)};
|
|
199
228
|
});
|
|
200
229
|
|
|
@@ -214,6 +243,7 @@ export default async function crosswordPlugin(fastify, options) {
|
|
|
214
243
|
if (!solved && !empty) p.state.penaltyMs = (p.state.penaltyMs || 0) + puzzle.wrongPenalty * 1000;
|
|
215
244
|
return solved ? finish(p) : p;
|
|
216
245
|
});
|
|
246
|
+
if (!play) return gone(reply);
|
|
217
247
|
if (solved) await afterFinish(puzzle, play);
|
|
218
248
|
return {
|
|
219
249
|
solved, empty, wrongCount: solved || empty ? 0 : wrong.length,
|
|
@@ -231,8 +261,11 @@ export default async function crosswordPlugin(fastify, options) {
|
|
|
231
261
|
const solved = play.status === 'finished';
|
|
232
262
|
return {
|
|
233
263
|
name: play.name, solved, timeMs: play.timeMs, penaltyMs: play.state.penaltyMs || 0, checks: play.state.checks || 0,
|
|
234
|
-
|
|
235
|
-
|
|
264
|
+
test: play.test === true,
|
|
265
|
+
place: solved && puzzle.leaderboard && !play.hidden && !play.test ? await plays.placeOf(play, 'all') : 0,
|
|
266
|
+
placeToday: solved && puzzle.leaderboard && !play.hidden && !play.test ? await plays.placeOf(play, 'today') : 0,
|
|
267
|
+
// A test is on no board; this is where it would have come.
|
|
268
|
+
wouldPlace: solved && puzzle.leaderboard && play.test ? await plays.placeOf(play, 'all') : 0,
|
|
236
269
|
leaderboard: puzzle.leaderboard,
|
|
237
270
|
// The answers only to someone who solved it: a timed-out play under another name must not be a way to read them.
|
|
238
271
|
solution: solved ? solutionGrid(puzzle.layout).map(row => row.map(ch => ch || ' ').join('')) : null
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crossword",
|
|
3
3
|
"displayName": "Crossword",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"tier": "free",
|
|
6
|
+
"minCmsVersion": "1.8.33",
|
|
6
7
|
"description": "Type answers and clues in the admin and the grid lays itself out. Put the crossword on any page: players type a name, solve against the clock, and the fastest go top of the leaderboard. The answers stay on the server until someone has solved it.",
|
|
7
8
|
"author": "Domma CMS",
|
|
8
|
-
"date": "2026-10-
|
|
9
|
+
"date": "2026-10-21",
|
|
9
10
|
"icon": "grid",
|
|
10
11
|
"permissions": [
|
|
11
12
|
{
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
],
|
|
71
72
|
"views": {
|
|
72
73
|
"plugin-crossword": {
|
|
73
|
-
"entry": "crossword/admin/views/crossword.js?v=
|
|
74
|
+
"entry": "crossword/admin/views/crossword.js?v=1.1.0",
|
|
74
75
|
"exportName": "crosswordView"
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -1 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
/* Crossword - the player. Shared pieces (cards, clocks, leaderboard) are in /plugins/_lib/public/games/games.css. */
|
|
2
|
+
.cw-app { max-width: 1040px; }
|
|
3
|
+
.cw-live { position: absolute; left: -9999px; }
|
|
4
|
+
.cw-clocks { display: inline-flex; align-items: center; gap: .5rem; }
|
|
5
|
+
.cw-current { margin: 0 0 .75rem; padding: .5rem .75rem; border-radius: 8px; font-weight: 600;
|
|
6
|
+
background: color-mix(in srgb, var(--gm-accent) 14%, transparent); }
|
|
7
|
+
|
|
8
|
+
.cw-layout { display: grid; grid-template-columns: auto minmax(14rem, 1fr); gap: 1.25rem; align-items: start; }
|
|
9
|
+
@media (max-width: 760px) { .cw-layout { grid-template-columns: 1fr; } }
|
|
10
|
+
.cw-grid-wrap { overflow-x: auto; max-width: 100%; }
|
|
11
|
+
/* A crossword is black and white whatever the theme: light squares, dark blocks, the theme's accent for the cursor. */
|
|
12
|
+
.cw-grid { --cw-size: clamp(1.6rem, calc((100vw - 4rem) / var(--cw-cols, 15)), 2.4rem); --cw-ink: #111827; --cw-paper: #ffffff;
|
|
13
|
+
display: inline-flex; flex-direction: column; border: 2px solid var(--cw-ink); background: var(--cw-ink); }
|
|
14
|
+
.cw-row { display: flex; }
|
|
15
|
+
.cw-cell { position: relative; width: var(--cw-size); height: var(--cw-size); box-sizing: border-box;
|
|
16
|
+
border: 1px solid #9ca3af; background: var(--cw-paper); }
|
|
17
|
+
.cw-cell.is-block { background: var(--cw-ink); border-color: var(--cw-ink); }
|
|
18
|
+
.cw-cell.is-word { background: color-mix(in srgb, var(--gm-accent) 22%, var(--cw-paper)); }
|
|
19
|
+
.cw-cell.is-cursor { background: color-mix(in srgb, var(--gm-accent) 55%, var(--cw-paper)); }
|
|
20
|
+
.cw-cell.is-wrong .cw-input { color: #dc2626; text-decoration: line-through; }
|
|
21
|
+
.cw-num { position: absolute; top: 1px; left: 2px; font-size: calc(var(--cw-size) * .28); line-height: 1; pointer-events: none; color: #4b5563; }
|
|
22
|
+
.cw-input { width: 100%; height: 100%; border: 0; padding: 0; margin: 0; background: transparent; text-align: center; text-transform: uppercase;
|
|
23
|
+
font: 700 calc(var(--cw-size) * .55)/1 var(--dm-font-sans, inherit); color: var(--cw-ink); caret-color: transparent; cursor: pointer; }
|
|
24
|
+
.cw-input:focus { outline: none; }
|
|
25
|
+
.cw-input[readonly] { cursor: default; }
|
|
26
|
+
|
|
27
|
+
.cw-clues { display: grid; grid-template-columns: 1fr; gap: 1rem; max-height: 34rem; overflow: auto; }
|
|
28
|
+
@media (min-width: 1000px) { .cw-clues { grid-template-columns: 1fr 1fr; } }
|
|
29
|
+
.cw-clues h3 { margin: 0 0 .35rem; font-size: 1rem; text-transform: uppercase; letter-spacing: .05em; }
|
|
30
|
+
.cw-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .1rem; }
|
|
31
|
+
.cw-clue { display: block; width: 100%; text-align: left; font: inherit; color: inherit; background: none; border: 0; padding: .3rem .45rem; border-radius: 6px; cursor: pointer; }
|
|
32
|
+
.cw-clue:hover { background: var(--gm-soft); }
|
|
33
|
+
.cw-list li.is-on .cw-clue { background: color-mix(in srgb, var(--gm-accent) 20%, transparent); }
|
|
34
|
+
.cw-message.is-bad { color: var(--gm-bad); font-weight: 600; }
|
|
35
|
+
.cw-message.is-good { color: var(--gm-good); font-weight: 600; }
|
|
36
|
+
.cw-result { margin-bottom: 1rem; }
|