@lessonkit/themes 1.4.0 → 1.6.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 (3) hide show
  1. package/README.md +22 -4
  2. package/base.css +484 -0
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -6,6 +6,14 @@
6
6
 
7
7
  Design tokens, presets, and CSS variable utilities for LessonKit.
8
8
 
9
+ ## When to install
10
+
11
+ - Custom theme presets beyond `ThemeProvider` defaults
12
+ - Generating `--lk-*` CSS variables for non-React shells
13
+ - Validating theme tokens against `theme-contract.v1.json`
14
+
15
+ `@lessonkit/react` includes `ThemeProvider` and depends on this package.
16
+
9
17
  ## Install
10
18
 
11
19
  ```bash
@@ -17,21 +25,31 @@ npm install @lessonkit/themes
17
25
  ```typescript
18
26
  import { getPresetTheme, mergeThemes, themeToCssVariables } from "@lessonkit/themes";
19
27
 
20
- const theme = mergeThemes(getPresetTheme("light"), { colors: { primary: "#0066cc" } });
28
+ const theme = mergeThemes(getPresetTheme("brand"), {
29
+ colors: { primary: "#0066cc" },
30
+ });
21
31
  const vars = themeToCssVariables(theme); // { "--lk-color-primary": "#0066cc", ... }
22
32
  ```
23
33
 
34
+ In React courses, prefer `ThemeProvider`:
35
+
36
+ ```tsx
37
+ import { ThemeProvider } from "@lessonkit/react";
38
+
39
+ <ThemeProvider mode="light" preset="brand">
40
+ <Course ... />
41
+ </ThemeProvider>
42
+ ```
43
+
24
44
  **Presets:** `default`, `light`, `dark`, `brand` via `getPresetTheme()`
25
45
 
26
46
  **Utilities:** `validateTheme()`, `mergeThemes()`, `themeToCssDeclarationBlock()`, `buildThemeCatalog()`
27
47
 
28
48
  **Assets:** `theme-contract.v1.json`, `theme-catalog.v1.json`, `base.css`
29
49
 
30
- Pair with `ThemeProvider` from `@lessonkit/react` for runtime theming.
31
-
32
50
  ## Docs
33
51
 
34
- [Theming reference](https://lessonkit.readthedocs.io/en/latest/reference/theming.html)
52
+ [Theming reference](https://lessonkit.readthedocs.io/en/latest/reference/theming.html) · [Theming & accessibility guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/theming-and-accessibility.html) · [TypeDoc API index](https://lessonkit.readthedocs.io/en/latest/reference/api.html)
35
53
 
36
54
  ## License
37
55
 
package/base.css CHANGED
@@ -28,3 +28,487 @@
28
28
  opacity: 0.55;
29
29
  cursor: not-allowed;
30
30
  }
31
+
32
+ /* InteractiveVideo — H5P-style cue overlay on the player */
33
+ .lk-interactive-video-stage {
34
+ position: relative;
35
+ width: 100%;
36
+ max-width: 100%;
37
+ overflow: hidden;
38
+ border-radius: var(--lk-radius-lg);
39
+ background: #000;
40
+ }
41
+
42
+ .lk-interactive-video-player {
43
+ display: block;
44
+ width: 100%;
45
+ vertical-align: top;
46
+ }
47
+
48
+ .lk-interactive-video-overlay {
49
+ position: absolute;
50
+ inset: 0;
51
+ z-index: 2;
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ padding: var(--lk-space-md);
56
+ visibility: hidden;
57
+ pointer-events: none;
58
+ opacity: 0;
59
+ transition: opacity 0.2s ease;
60
+ }
61
+
62
+ .lk-interactive-video-overlay--active {
63
+ visibility: visible;
64
+ pointer-events: auto;
65
+ opacity: 1;
66
+ }
67
+
68
+ .lk-interactive-video-backdrop {
69
+ position: absolute;
70
+ inset: 0;
71
+ background: rgba(0, 0, 0, 0.72);
72
+ }
73
+
74
+ .lk-interactive-video-overlay-content {
75
+ position: relative;
76
+ z-index: 1;
77
+ display: flex;
78
+ flex-direction: column;
79
+ align-items: stretch;
80
+ gap: var(--lk-space-md);
81
+ width: min(100%, 36rem);
82
+ max-height: 100%;
83
+ overflow: auto;
84
+ }
85
+
86
+ .lk-interactive-video-cues {
87
+ width: 100%;
88
+ }
89
+
90
+ .lk-timed-cue-overlay[hidden] {
91
+ display: none !important;
92
+ }
93
+
94
+ .lk-timed-cue-overlay:not([hidden]) {
95
+ background: var(--lk-color-panel);
96
+ color: var(--lk-color-foreground);
97
+ border-radius: var(--lk-radius-lg);
98
+ padding: var(--lk-space-lg);
99
+ box-shadow: var(--lk-shadow-lg);
100
+ max-height: min(70vh, 28rem);
101
+ overflow: auto;
102
+ }
103
+
104
+ .lk-timed-cue-label {
105
+ margin: 0 0 var(--lk-space-sm);
106
+ font-size: var(--lk-font-size-lg);
107
+ font-weight: var(--lk-font-weight-strong);
108
+ line-height: var(--lk-line-height-tight);
109
+ }
110
+
111
+ .lk-interactive-video-overlay-actions {
112
+ display: flex;
113
+ flex-direction: column;
114
+ align-items: center;
115
+ gap: var(--lk-space-sm);
116
+ width: 100%;
117
+ }
118
+
119
+ .lk-interactive-video-continue {
120
+ min-width: 10rem;
121
+ }
122
+
123
+ /* GameMap — background canvas and stage markers */
124
+ .lk-game-map-canvas {
125
+ position: relative;
126
+ width: 100%;
127
+ max-width: 100%;
128
+ aspect-ratio: 16 / 9;
129
+ overflow: hidden;
130
+ border-radius: var(--lk-radius-lg);
131
+ background: var(--lk-color-panel);
132
+ border: 1px solid var(--lk-color-border);
133
+ }
134
+
135
+ .lk-game-map-background {
136
+ position: absolute;
137
+ inset: 0;
138
+ display: block;
139
+ width: 100%;
140
+ height: 100%;
141
+ object-fit: cover;
142
+ pointer-events: none;
143
+ user-select: none;
144
+ }
145
+
146
+ .lk-game-map-blocked {
147
+ position: absolute;
148
+ inset: 0;
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ margin: 0;
153
+ padding: var(--lk-space-md);
154
+ text-align: center;
155
+ background: var(--lk-color-panel);
156
+ color: var(--lk-color-foreground);
157
+ }
158
+
159
+ .lk-game-map-marker {
160
+ position: absolute;
161
+ transform: translate(-50%, -100%);
162
+ padding: 0.35rem 0.65rem;
163
+ border: 2px solid #fff;
164
+ border-radius: 999px;
165
+ background: var(--lk-color-primary);
166
+ color: var(--lk-color-primary-foreground, #fff);
167
+ font-family: var(--lk-font-family);
168
+ font-size: 0.8125rem;
169
+ font-weight: var(--lk-font-weight-strong);
170
+ line-height: 1.2;
171
+ box-shadow: 0 2px 8px rgba(15, 23, 42, 0.35);
172
+ cursor: pointer;
173
+ z-index: 1;
174
+ }
175
+
176
+ .lk-game-map-marker::after {
177
+ content: "";
178
+ position: absolute;
179
+ left: 50%;
180
+ bottom: -8px;
181
+ width: 0;
182
+ height: 0;
183
+ transform: translateX(-50%);
184
+ border-left: 7px solid transparent;
185
+ border-right: 7px solid transparent;
186
+ border-top: 8px solid var(--lk-color-primary);
187
+ }
188
+
189
+ .lk-game-map-marker--active {
190
+ background: var(--lk-color-accent, #f59e0b);
191
+ color: #111827;
192
+ z-index: 2;
193
+ }
194
+
195
+ .lk-game-map-marker--active::after {
196
+ border-top-color: var(--lk-color-accent, #f59e0b);
197
+ }
198
+
199
+ .lk-game-map-marker:disabled {
200
+ opacity: 0.45;
201
+ cursor: not-allowed;
202
+ background: #64748b;
203
+ }
204
+
205
+ .lk-game-map-marker:disabled::after {
206
+ border-top-color: #64748b;
207
+ }
208
+
209
+ .lk-game-map-marker--reachable:not(:disabled) {
210
+ background: var(--lk-color-success, #16a34a);
211
+ animation: lk-map-marker-pulse 1.6s ease-in-out infinite;
212
+ }
213
+
214
+ .lk-game-map-marker--reachable:not(:disabled)::after {
215
+ border-top-color: var(--lk-color-success, #16a34a);
216
+ }
217
+
218
+ @keyframes lk-map-marker-pulse {
219
+ 0%,
220
+ 100% {
221
+ box-shadow: 0 0 0 0 rgba(22, 163, 74, 0.45);
222
+ }
223
+ 50% {
224
+ box-shadow: 0 0 0 8px rgba(22, 163, 74, 0);
225
+ }
226
+ }
227
+
228
+ .lk-game-map-active-stage {
229
+ margin-top: var(--lk-space-md);
230
+ padding: var(--lk-space-md);
231
+ border: 1px solid var(--lk-color-border);
232
+ border-radius: var(--lk-radius-lg);
233
+ background: var(--lk-color-panel);
234
+ }
235
+
236
+ .lk-map-exit {
237
+ display: inline-flex;
238
+ margin: 0.35rem 0.5rem 0.35rem 0;
239
+ }
240
+
241
+ /* Crossword — square grid cells with block squares */
242
+ .lk-crossword {
243
+ --lk-crossword-cell-size: 2.25rem;
244
+ --lk-crossword-block: #0f172a;
245
+ }
246
+
247
+ .lk-crossword-grid {
248
+ display: inline-flex;
249
+ flex-direction: column;
250
+ gap: 1px;
251
+ margin-bottom: var(--lk-space-md);
252
+ border: 2px solid var(--lk-crossword-block);
253
+ background: var(--lk-crossword-block);
254
+ line-height: 0;
255
+ }
256
+
257
+ .lk-crossword-row {
258
+ display: flex;
259
+ gap: 1px;
260
+ }
261
+
262
+ .lk-crossword-cell {
263
+ position: relative;
264
+ width: var(--lk-crossword-cell-size);
265
+ height: var(--lk-crossword-cell-size);
266
+ flex: 0 0 var(--lk-crossword-cell-size);
267
+ box-sizing: border-box;
268
+ }
269
+
270
+ .lk-crossword-cell--block {
271
+ background: var(--lk-crossword-block);
272
+ }
273
+
274
+ .lk-crossword-cell input {
275
+ display: block;
276
+ width: 100%;
277
+ height: 100%;
278
+ margin: 0;
279
+ padding: 0;
280
+ border: none;
281
+ box-sizing: border-box;
282
+ text-align: center;
283
+ text-transform: uppercase;
284
+ font-family: var(--lk-font-family);
285
+ font-size: 1.0625rem;
286
+ font-weight: var(--lk-font-weight-strong);
287
+ line-height: var(--lk-crossword-cell-size);
288
+ background: #fff;
289
+ color: #0f172a;
290
+ cursor: text;
291
+ }
292
+
293
+ .lk-crossword-cell input:focus {
294
+ outline: 2px solid var(--lk-color-primary);
295
+ outline-offset: -2px;
296
+ position: relative;
297
+ z-index: 1;
298
+ }
299
+
300
+ .lk-crossword-cell input:read-only {
301
+ background: #f8fafc;
302
+ cursor: default;
303
+ }
304
+
305
+ .lk-crossword-clue-num {
306
+ position: absolute;
307
+ top: 2px;
308
+ left: 3px;
309
+ z-index: 2;
310
+ font-size: 0.5625rem;
311
+ font-weight: 700;
312
+ line-height: 1;
313
+ color: #334155;
314
+ pointer-events: none;
315
+ user-select: none;
316
+ }
317
+
318
+ .lk-crossword-clues {
319
+ display: grid;
320
+ gap: var(--lk-space-md);
321
+ margin-bottom: var(--lk-space-md);
322
+ }
323
+
324
+ @media (min-width: 36rem) {
325
+ .lk-crossword-clues {
326
+ grid-template-columns: 1fr 1fr;
327
+ }
328
+ }
329
+
330
+ .lk-crossword-clues-heading {
331
+ margin: 0 0 var(--lk-space-xs);
332
+ font-family: var(--lk-font-family);
333
+ font-size: 0.875rem;
334
+ font-weight: var(--lk-font-weight-strong);
335
+ text-transform: uppercase;
336
+ letter-spacing: 0.04em;
337
+ color: var(--lk-color-muted-foreground, #64748b);
338
+ }
339
+
340
+ .lk-crossword-clue-list {
341
+ margin: 0;
342
+ padding-left: 1.25rem;
343
+ font-family: var(--lk-font-family);
344
+ font-size: var(--lk-font-size-base);
345
+ line-height: 1.45;
346
+ }
347
+
348
+ .lk-crossword-clue-label {
349
+ font-weight: var(--lk-font-weight-strong);
350
+ }
351
+
352
+ /* DragAndDrop — draggable chips and drop zones */
353
+ .lk-drag-pool {
354
+ display: flex;
355
+ flex-wrap: wrap;
356
+ align-items: center;
357
+ gap: 0.35rem;
358
+ min-height: 2.25rem;
359
+ margin-bottom: var(--lk-space-md);
360
+ padding: 0.35rem 0.45rem;
361
+ border: 1px dashed var(--lk-color-border);
362
+ border-radius: var(--lk-radius-md);
363
+ background: var(--lk-color-panel);
364
+ transition:
365
+ border-color 0.15s ease,
366
+ background-color 0.15s ease,
367
+ padding 0.15s ease;
368
+ }
369
+
370
+ .lk-drag-pool--hover {
371
+ border-color: var(--lk-color-primary);
372
+ background: color-mix(in srgb, var(--lk-color-primary) 8%, var(--lk-color-panel));
373
+ }
374
+
375
+ .lk-drag-pool-placeholder {
376
+ padding: 0.3rem 0.55rem;
377
+ color: var(--lk-color-muted-foreground, #64748b);
378
+ font-family: var(--lk-font-family);
379
+ font-size: 0.8125rem;
380
+ line-height: 1.2;
381
+ white-space: nowrap;
382
+ transition: opacity 0.12s ease;
383
+ }
384
+
385
+ .lk-drag-item {
386
+ appearance: none;
387
+ margin: 0;
388
+ padding: 0.35rem 0.7rem;
389
+ border: 1px solid var(--lk-color-border);
390
+ border-radius: 999px;
391
+ background: var(--lk-color-background, #fff);
392
+ color: var(--lk-color-foreground);
393
+ font-family: var(--lk-font-family);
394
+ font-size: 0.875rem;
395
+ font-weight: var(--lk-font-weight-strong);
396
+ cursor: grab;
397
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
398
+ }
399
+
400
+ .lk-drag-item:active {
401
+ cursor: grabbing;
402
+ }
403
+
404
+ .lk-drag-item[aria-pressed="true"] {
405
+ outline: 2px solid var(--lk-color-primary);
406
+ outline-offset: 2px;
407
+ }
408
+
409
+ .lk-drag-item--in-flight {
410
+ opacity: 0;
411
+ pointer-events: none;
412
+ }
413
+
414
+ .lk-drag-flyer {
415
+ position: fixed;
416
+ z-index: 9999;
417
+ margin: 0;
418
+ padding: 0.35rem 0.7rem;
419
+ border: 1px solid var(--lk-color-border);
420
+ border-radius: 999px;
421
+ background: var(--lk-color-background, #fff);
422
+ color: var(--lk-color-foreground);
423
+ font-family: var(--lk-font-family);
424
+ font-size: 0.875rem;
425
+ font-weight: var(--lk-font-weight-strong);
426
+ box-shadow: 0 4px 14px rgba(15, 23, 42, 0.18);
427
+ pointer-events: none;
428
+ transform: translate(-50%, -50%);
429
+ }
430
+
431
+ .lk-drag-item--ghost {
432
+ opacity: 0.6;
433
+ border-style: dashed;
434
+ cursor: default;
435
+ pointer-events: none;
436
+ box-shadow: none;
437
+ }
438
+
439
+ .lk-drag-target-row {
440
+ margin-bottom: 0.5rem;
441
+ }
442
+
443
+ .lk-drag-target {
444
+ display: inline-flex;
445
+ align-items: center;
446
+ justify-content: center;
447
+ width: fit-content;
448
+ max-width: 100%;
449
+ padding: 0.1rem;
450
+ border: 1px dashed var(--lk-color-border);
451
+ border-radius: var(--lk-radius-md);
452
+ background: color-mix(in srgb, var(--lk-color-panel) 80%, transparent);
453
+ vertical-align: middle;
454
+ transition:
455
+ width 0.15s ease,
456
+ min-width 0.15s ease,
457
+ padding 0.15s ease,
458
+ border-color 0.15s ease,
459
+ background-color 0.15s ease;
460
+ }
461
+
462
+ .lk-drag-target--hover {
463
+ border-color: var(--lk-color-primary);
464
+ background: color-mix(in srgb, var(--lk-color-primary) 8%, var(--lk-color-panel));
465
+ }
466
+
467
+ .lk-drag-target-placeholder {
468
+ display: inline-block;
469
+ padding: 0.3rem 0.55rem;
470
+ color: var(--lk-color-muted-foreground, #64748b);
471
+ font-family: var(--lk-font-family);
472
+ font-size: 0.8125rem;
473
+ line-height: 1.2;
474
+ white-space: nowrap;
475
+ transition: opacity 0.12s ease;
476
+ }
477
+
478
+ .lk-crossword-cell--correct input {
479
+ background: #dcfce7;
480
+ color: #166534;
481
+ }
482
+
483
+ .lk-crossword-cell--incorrect input {
484
+ background: #fee2e2;
485
+ color: #991b1b;
486
+ }
487
+
488
+ .lk-crossword-actions {
489
+ display: flex;
490
+ flex-wrap: wrap;
491
+ gap: var(--lk-space-sm);
492
+ margin-bottom: var(--lk-space-sm);
493
+ }
494
+
495
+ .lk-crossword-feedback {
496
+ margin: 0 0 var(--lk-space-sm);
497
+ padding: 0.65rem 0.85rem;
498
+ border-radius: var(--lk-radius-md);
499
+ font-family: var(--lk-font-family);
500
+ font-size: var(--lk-font-size-base);
501
+ font-weight: var(--lk-font-weight-strong);
502
+ }
503
+
504
+ .lk-crossword-feedback--success {
505
+ background: #dcfce7;
506
+ color: #166534;
507
+ border: 1px solid #86efac;
508
+ }
509
+
510
+ .lk-crossword-feedback--retry {
511
+ background: #fef3c7;
512
+ color: #92400e;
513
+ border: 1px solid #fcd34d;
514
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/themes",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "private": false,
5
5
  "description": "Theme primitives and tokens for LessonKit.",
6
6
  "license": "Apache-2.0",
@@ -45,11 +45,11 @@
45
45
  "typecheck": "tsc -p tsconfig.json",
46
46
  "test": "vitest run",
47
47
  "test:coverage": "vitest run --coverage --passWithNoTests=false",
48
- "lint": "echo \"(no lint configured yet)\""
48
+ "lint": "eslint --max-warnings 0 \"src/**/*.{ts,tsx}\" \"test/**/*.{ts,tsx}\""
49
49
  },
50
50
  "devDependencies": {
51
51
  "tsup": "^8.5.0",
52
- "typescript": "^5.8.3",
52
+ "typescript": "^6.0.3",
53
53
  "vitest": "^4.1.8"
54
54
  }
55
55
  }