mdeck 3.6.2 → 3.7.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.7.0 — 2026-10-09
4
+
5
+ - Palettes have a third accent, `--accent-3`, and the three accents mean something: `--accent` is emphasis, `--accent-2` its companion (gradients, a contrast to the accent), `--accent-3` attention, used sparingly. `--accent-3` marks things and need not be readable as text, so it may be a yellow; `mdeck check` asks only that it can be seen (1.5:1 against the background). A palette without `--accent-3` keeps working and gets an amber (`#d99a00`, at night `#f5c542`); `mdeck check` suggests giving it its own. All built-in palettes and those in the theme repository set it (the repository requires it); `mdeck palettes update` brings installed ones up to date.
6
+ - Callouts take their colours from the palette: note, tip and important the accent, warning and caution the third accent. The five callout colours (`--callout-note` … `--callout-caution`) are gone, and with them the green, purple and blue that matched no palette. The built-in themes follow.
7
+
3
8
  ## 3.6.2 — 2026-10-09
4
9
 
5
10
  - The docs home page and the README start with what mdeck does: drawing from an iPad, polls from phones, citations, themes, running code and sending one file, each with a picture (`npm run feature-images` renders them). The docs' example deck is now a short tour of mdeck, with its own control bar and drawing, and a picker of every built-in theme as a picture, with light or dark.
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#d5dbf0"
17
17
  "--accent" = "#1f3fd1"
18
18
  "--accent-2" = "#e8590c"
19
+ "--accent-3" = "#e8a400"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#212b5e"
28
29
  "--accent" = "#6f8cff"
29
30
  "--accent-2" = "#ff922b"
31
+ "--accent-3" = "#ffd43b"
30
32
  "--on-accent" = "#0a1030"
@@ -8,8 +8,8 @@ author = "Tilman Schieber"
8
8
  license = "MIT"
9
9
 
10
10
  # No colour anywhere: emphasis is black against the softer body text, the
11
- # second accent a mid grey; code and callouts use greys instead of their
12
- # usual colours.
11
+ # second accent a mid grey, the third (warnings) a lighter grey; code uses
12
+ # greys instead of its usual colours.
13
13
  [light]
14
14
  "--bg" = "#ffffff"
15
15
  "--surface" = "#f0f0f0"
@@ -19,6 +19,7 @@ license = "MIT"
19
19
  "--rule" = "#d4d4d4"
20
20
  "--accent" = "#000000"
21
21
  "--accent-2" = "#6e6e6e"
22
+ "--accent-3" = "#9a9a9a"
22
23
  "--on-accent" = "#ffffff"
23
24
  "--token-default" = "#141414"
24
25
  "--token-comment" = "#6b6b6b"
@@ -27,11 +28,6 @@ license = "MIT"
27
28
  "--token-keyword" = "#000000"
28
29
  "--token-function" = "#2a2a2a"
29
30
  "--token-operator" = "#555555"
30
- "--callout-note" = "#4a4a4a"
31
- "--callout-tip" = "#4a4a4a"
32
- "--callout-important" = "#000000"
33
- "--callout-warning" = "#7a7a7a"
34
- "--callout-caution" = "#000000"
35
31
 
36
32
  [dark]
37
33
  "--bg" = "#121212"
@@ -42,6 +38,7 @@ license = "MIT"
42
38
  "--rule" = "#303030"
43
39
  "--accent" = "#ffffff"
44
40
  "--accent-2" = "#a6a6a6"
41
+ "--accent-3" = "#6e6e6e"
45
42
  "--on-accent" = "#121212"
46
43
  "--token-default" = "#f2f2f2"
47
44
  "--token-comment" = "#9a9a9a"
@@ -50,8 +47,3 @@ license = "MIT"
50
47
  "--token-keyword" = "#ffffff"
51
48
  "--token-function" = "#e0e0e0"
52
49
  "--token-operator" = "#b0b0b0"
53
- "--callout-note" = "#b8b8b8"
54
- "--callout-tip" = "#b8b8b8"
55
- "--callout-important" = "#ffffff"
56
- "--callout-warning" = "#8c8c8c"
57
- "--callout-caution" = "#ffffff"
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#e0deda"
17
17
  "--accent" = "#0d9488"
18
18
  "--accent-2" = "#c2410c"
19
+ "--accent-3" = "#ca8a04"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#23332f"
28
29
  "--accent" = "#2dd4bf"
29
30
  "--accent-2" = "#fb923c"
31
+ "--accent-3" = "#facc15"
30
32
  "--on-accent" = "#062b26"
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#cdd8e1"
17
17
  "--accent" = "#1f6f9f"
18
18
  "--accent-2" = "#7a5c9e"
19
+ "--accent-3" = "#c08a1e"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#22343f"
28
29
  "--accent" = "#7cc4ea"
29
30
  "--accent-2" = "#c3a8e6"
31
+ "--accent-3" = "#e8c170"
30
32
  "--on-accent" = "#0e1a24"
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#e0ddd6"
17
17
  "--accent" = "#c2410c"
18
18
  "--accent-2" = "#1f2937"
19
+ "--accent-3" = "#c99700"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#2e2e2c"
28
29
  "--accent" = "#fb8c4a"
29
30
  "--accent-2" = "#e7e5e0"
31
+ "--accent-3" = "#f2c94c"
30
32
  "--on-accent" = "#151515"
@@ -16,6 +16,7 @@ license = "MIT"
16
16
  "--rule" = "#dadada"
17
17
  "--accent" = "#e30613"
18
18
  "--accent-2" = "#0047bb"
19
+ "--accent-3" = "#f2b700"
19
20
  "--on-accent" = "#ffffff"
20
21
 
21
22
  [dark]
@@ -27,4 +28,5 @@ license = "MIT"
27
28
  "--rule" = "#2e2e2e"
28
29
  "--accent" = "#ff3b30"
29
30
  "--accent-2" = "#6f9bff"
31
+ "--accent-3" = "#ffd60a"
30
32
  "--on-accent" = "#0d0d0d"
@@ -476,11 +476,9 @@ strong { color: var(--ink); font-weight: 700; }
476
476
  .slide-body .callout p { color: var(--ink); }
477
477
  .slide-body .callout :is(ul, ol) { gap: 8px; }
478
478
 
479
- .slide-body :is(.callout-theorem, .callout-lemma, .callout-corollary, .callout-proposition, .callout-tip, .callout-important) { --block: var(--accent); }
479
+ .slide-body :is(.callout-theorem, .callout-lemma, .callout-corollary, .callout-proposition, .callout-note, .callout-tip, .callout-important) { --block: var(--accent); }
480
480
  .slide-body :is(.callout-definition) { --block: var(--accent-2); }
481
- .slide-body .callout-note { --block: var(--callout-note); }
482
- .slide-body .callout-warning { --block: var(--callout-warning); }
483
- .slide-body .callout-caution { --block: var(--callout-caution); }
481
+ .slide-body :is(.callout-warning, .callout-caution) { --block: var(--accent-3); }
484
482
  .slide-body :is(.callout-theorem, .callout-lemma, .callout-corollary, .callout-proposition) > :not(.callout-title) {
485
483
  font-family: var(--font-serif);
486
484
  font-style: italic;
@@ -640,13 +640,9 @@ deck-stage[data-deck-static] .slide { animation: none; }
640
640
  box-shadow: 0 0 10px currentColor;
641
641
  }
642
642
  .slide-body .callout p { padding-left: 0; margin: 0; color: var(--ink); }
643
- .slide-body .callout-note { --tint: var(--callout-note); }
644
- .slide-body .callout-tip { --tint: var(--callout-tip); }
645
- .slide-body .callout-important { --tint: var(--callout-important); }
646
- .slide-body .callout-warning { --tint: var(--callout-warning); }
647
- .slide-body .callout-caution { --tint: var(--callout-caution); }
643
+ .slide-body :is(.callout-note, .callout-tip, .callout-important) { --tint: var(--accent); }
644
+ .slide-body :is(.callout-warning, .callout-caution) { --tint: var(--accent-3); }
648
645
  .slide-body .callout-definition { --tint: var(--accent-2); }
649
- .slide-body .callout-tip .callout-title { color: color-mix(in oklab, var(--callout-tip) 60%, var(--ink)); }
650
646
  .slide-body :is(.callout-theorem, .callout-lemma, .callout-corollary) .callout-title { color: var(--em); }
651
647
  .slide-body .callout-definition .callout-title { color: light-dark(color-mix(in oklab, var(--accent-2) 80%, var(--ink)), var(--accent-2)); }
652
648
 
@@ -297,11 +297,8 @@ strong { color: var(--ink); font-weight: 600; }
297
297
  margin: 32px 0 0;
298
298
  max-width: 1200px;
299
299
  }
300
- .slide-body .callout-tip { border-left-color: var(--accent); }
301
- .slide-body .callout-note { border-left-color: var(--callout-note); }
302
- .slide-body .callout-important { border-left-color: var(--callout-important); }
303
- .slide-body .callout-warning { border-left-color: var(--callout-warning); }
304
- .slide-body .callout-caution { border-left-color: var(--callout-caution); }
300
+ .slide-body :is(.callout-note, .callout-tip, .callout-important) { border-left-color: var(--accent); }
301
+ .slide-body :is(.callout-warning, .callout-caution) { border-left-color: var(--accent-3); }
305
302
  .slide-body .callout-title { margin-bottom: 8px; }
306
303
  .slide-body .callout p { padding-left: 0; color: var(--ink); font-size: var(--fs-body); }
307
304
  /* Lecture blocks: theorems in the accent, definitions in the second */
@@ -434,12 +434,9 @@ strong { color: var(--ink); font-weight: 600; }
434
434
  margin: 40px 0 0;
435
435
  max-width: 1200px;
436
436
  }
437
- .slide-body .callout-tip { border-top-color: var(--accent); }
437
+ .slide-body :is(.callout-note, .callout-tip, .callout-important) { border-top-color: var(--accent); }
438
438
  .slide-body .callout-tip .callout-title { color: var(--accent); }
439
- .slide-body .callout-note { border-top-color: var(--callout-note); }
440
- .slide-body .callout-important { border-top-color: var(--callout-important); }
441
- .slide-body .callout-warning { border-top-color: var(--callout-warning); }
442
- .slide-body .callout-caution { border-top-color: var(--callout-caution); }
439
+ .slide-body :is(.callout-warning, .callout-caution) { border-top-color: var(--accent-3); }
443
440
  .slide-body .callout-title { letter-spacing: 0.12em; margin-bottom: 10px; }
444
441
  .slide-body .callout p { padding-left: 0; color: var(--ink); font-size: var(--fs-body); }
445
442
  /* Lecture blocks: theorems under a rule in the accent, definitions in the second */
@@ -418,8 +418,7 @@ strong { font-weight: 600; color: var(--ink); }
418
418
  }
419
419
  .slide-body .callout p { padding-left: 0; margin: 0; color: var(--ink-soft); }
420
420
  .slide-body .callout :is(.callout-title) { color: var(--ink); }
421
- .slide-body .callout:is(.callout-warning, .callout-caution) { border-left-color: var(--callout-warning); }
422
- .slide-body .callout.callout-caution { border-left-color: var(--callout-caution); }
421
+ .slide-body .callout:is(.callout-warning, .callout-caution) { border-left-color: var(--accent-3); }
423
422
  .slide-body .callout.callout-proof { background: none; border: 0; padding: 0; }
424
423
 
425
424
  /* ─────────────────────────────────────────────
@@ -448,11 +448,8 @@ strong { font-weight: 600; color: var(--ink); }
448
448
  margin: 0 0 8px 0;
449
449
  }
450
450
  .slide-body .callout p { padding-left: 0; margin: 0; color: var(--ink); }
451
- .slide-body .callout-note { --bar: var(--callout-note); }
452
- .slide-body .callout-tip { --bar: var(--callout-tip); }
453
- .slide-body .callout-important { --bar: var(--callout-important); }
454
- .slide-body .callout-warning { --bar: var(--callout-warning); }
455
- .slide-body .callout-caution { --bar: var(--callout-caution); }
451
+ .slide-body :is(.callout-note, .callout-tip, .callout-important) { --bar: var(--accent); }
452
+ .slide-body :is(.callout-warning, .callout-caution) { --bar: var(--accent-3); }
456
453
  .slide-body .callout-definition { --bar: var(--accent-2); }
457
454
 
458
455
  /* ─────────────────────────────────────────────
@@ -686,7 +686,7 @@ A local SVG whose source contains `var(--…)` is drawn inline instead of as an
686
686
  layout's `image` (`title`, `chapter`, `image-text`, `full-bleed-image`, and
687
687
  local layouts that render it with `Picture` from `mdeck/layout`). It can use the
688
688
  palette's custom properties (`--bg`, `--surface`, `--ink`, `--ink-soft`,
689
- `--muted`, `--rule`, `--accent`, `--accent-2`, `--on-accent`) and follows the
689
+ `--muted`, `--rule`, `--accent`, `--accent-2`, `--accent-3`, `--on-accent`) and follows the
690
690
  theme, palette and appearance. Give each a fallback (`var(--accent, #1f3fd1)`)
691
691
  for places that show the file on its own, set `fit: contain` to keep the whole
692
692
  drawing, prefix class names in its `<style>` because inline styles apply to the
@@ -83,6 +83,7 @@ description = "Deep blue with a warm highlight."
83
83
  "--rule" = "#c9d7e2"
84
84
  "--accent" = "#0a6aa8"
85
85
  "--accent-2" = "#b45309"
86
+ "--accent-3" = "#c99700"
86
87
  "--on-accent" = "#ffffff"
87
88
 
88
89
  [dark]
@@ -94,6 +95,7 @@ description = "Deep blue with a warm highlight."
94
95
  "--rule" = "#24384b"
95
96
  "--accent" = "#ffbd69"
96
97
  "--accent-2" = "#7cc4ff"
98
+ "--accent-3" = "#f2c94c"
97
99
  "--on-accent" = "#102030"
98
100
  ```
99
101
 
@@ -23,7 +23,7 @@ The presenter view and the reader view switch palette and light or dark without
23
23
  | `swiss` | white, black and signal red | black, white and red |
24
24
  | `nordic` | ice grey, deep navy and a cool steel blue | polar night with ice blue |
25
25
  | `cobalt` | strong ultramarine on white, vivid orange | midnight blue, bright blue and orange |
26
- | `greyscale` | black, white and greys only, code and callouts too | the same, inverted |
26
+ | `greyscale` | black, white and greys only, code too | the same, inverted |
27
27
  | `lagoon` | teal on warm stone, orange second accent | deep teal-black, bright teal |
28
28
  | `paper` | off-white paper and near-black ink with one orange accent | charcoal with a softer orange |
29
29
 
@@ -35,7 +35,7 @@ More palettes are in the [theme repository](https://github.com/tilman-schieber/m
35
35
 
36
36
  ## Colours
37
37
 
38
- Every variant sets all nine:
38
+ Every variant sets all ten (a palette without `--accent-3` gets an amber, and `mdeck check` suggests setting one):
39
39
 
40
40
  | Colour | Role |
41
41
  |---|---|
@@ -45,8 +45,9 @@ Every variant sets all nine:
45
45
  | `--ink-soft` | Body text |
46
46
  | `--muted` | Small labels: headers, footers, captions |
47
47
  | `--rule` | Lines and dividers |
48
- | `--accent` | The highlight: emphasis, markers, chapter slides |
49
- | `--accent-2` | A second highlight for themes that pair two |
48
+ | `--accent` | Emphasis: highlighted words, markers, chapter slides; notes, tips and important callouts |
49
+ | `--accent-2` | The accent's companion: gradients, a contrast to the accent, definitions |
50
+ | `--accent-3` | Attention: warnings and cautions, and a colour that pops, used sparingly. It marks things (bars, shapes) and need not be readable as text, so it may be a yellow |
50
51
  | `--on-accent` | Text set on the accent, such as a chapter slide's title |
51
52
 
52
53
  Themes may mix them (for example a light tint of the accent), and read the other variant as `--inverse-bg`, `--inverse-ink`, `--inverse-accent` and so on.
@@ -71,6 +72,7 @@ description = "Sea blue and rust on chalk white."
71
72
  "--rule" = "#c9d7e2"
72
73
  "--accent" = "#0a6aa8"
73
74
  "--accent-2" = "#b45309"
75
+ "--accent-3" = "#c99700"
74
76
  "--on-accent" = "#ffffff"
75
77
 
76
78
  [dark]
@@ -82,6 +84,7 @@ description = "Sea blue and rust on chalk white."
82
84
  "--rule" = "#24384b"
83
85
  "--accent" = "#5fb3f0"
84
86
  "--accent-2" = "#f59e5b"
87
+ "--accent-3" = "#f2c94c"
85
88
  "--on-accent" = "#0d1a24"
86
89
  ```
87
90
 
@@ -48,6 +48,7 @@ title = "Blue notebook"
48
48
  "--rule" = "#dbe3ee"
49
49
  "--accent" = "#2455c7"
50
50
  "--accent-2" = "#17625c"
51
+ "--accent-3" = "#c99700"
51
52
  "--on-accent" = "#ffffff"
52
53
 
53
54
  [dark]
@@ -59,6 +60,7 @@ title = "Blue notebook"
59
60
  "--rule" = "#24354d"
60
61
  "--accent" = "#7aa2ff"
61
62
  "--accent-2" = "#5ec4b6"
63
+ "--accent-3" = "#f2c94c"
62
64
  "--on-accent" = "#0f1a2b"
63
65
  ```
64
66
 
@@ -76,11 +78,12 @@ The folder must be called `notebook` to match `id`. Use it with `palette: notebo
76
78
  | `--ink-soft` | Body text |
77
79
  | `--muted` | Secondary labels |
78
80
  | `--rule` | Borders |
79
- | `--accent` | Main highlight |
80
- | `--accent-2` | Secondary highlight |
81
+ | `--accent` | Emphasis; notes, tips and important callouts |
82
+ | `--accent-2` | The accent's companion: gradients, a contrast to the accent |
83
+ | `--accent-3` | Attention: warnings and cautions. Use it sparingly, for bars and shapes, never as text: it may be a yellow |
81
84
  | `--on-accent` | Text placed on an accent background |
82
85
 
83
- A theme has no colours of its own: it uses these, and mixes of them, so every palette repaints it. For an inverted slide it uses the palette's other version, as `--inverse-bg`, `--inverse-ink`, `--inverse-accent` and so on:
86
+ A theme has no colours of its own: it uses these, and mixes of them, so every palette repaints it. Give each accent the job its name says: a theme that colours callouts colours note, tip and important with `--accent` and warning and caution with `--accent-3`. For an inverted slide it uses the palette's other version, as `--inverse-bg`, `--inverse-ink`, `--inverse-accent` and so on:
84
87
 
85
88
  ```css
86
89
  .slide--focus {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdeck",
3
- "version": "3.6.2",
3
+ "version": "3.7.0",
4
4
  "type": "module",
5
5
  "description": "Write slides as a plain text file, present them in the browser, share them as one HTML file or PDF.",
6
6
  "license": "MIT",
@@ -142,7 +142,7 @@ To see slides yourself, if you can read pictures, run `mdeck snapshot <deck>.md
142
142
 
143
143
  When the user wants a look the built-in themes and palettes do not give (their institute's colours, a font, the style of a poster or picture), first run `mdeck themes search` or `mdeck palettes search` (with a word or two): someone may have shared it, and `mdeck themes install <theme> <deck>.md` (with its palette) or `mdeck palettes install <palette> <deck>.md` puts it beside the deck, so the slide folder carries it. Otherwise make it as an extension in `extensions/` beside the deck, never as styling on single slides. Read `extensions.md` from the reference folder first; it lists every setting.
144
144
 
145
- - **Colours only: a palette.** `extensions/<id>/extension.toml` with `kind = "palette"`, and every colour role (`--bg`, `--surface`, `--ink`, `--ink-soft`, `--muted`, `--rule`, `--accent`, `--accent-2`, `--on-accent`) in both `[light]` and `[dark]`. Text colours must stay readable on `--bg` and `--surface` in both variants, because themes use the other variant for inverted slides.
145
+ - **Colours only: a palette.** `extensions/<id>/extension.toml` with `kind = "palette"`, and every colour role (`--bg`, `--surface`, `--ink`, `--ink-soft`, `--muted`, `--rule`, `--accent`, `--accent-2`, `--accent-3`, `--on-accent`) in both `[light]` and `[dark]`: `--accent` is emphasis, `--accent-2` its companion, `--accent-3` attention (warnings; it may be too light for text, such as a yellow). Set all of them. Text colours must stay readable on `--bg` and `--surface` in both variants, because themes use the other variant for inverted slides.
146
146
  - **Fonts, sizes, spacing or arrangement: a theme.** Copy the folder of the closest built-in theme (`assets/extensions/themes/<id>/` in the mdeck repository or in `$(npm root -g)/mdeck/`) to `extensions/<new-id>/`, change `id` and `title`, then its tokens and `styles.css`. A theme takes no colours of its own: it names its default `palette`, and the colours come from palettes.
147
147
  - **Use it:** set `theme:` and `palette:` in the deck settings. `mdeck list <deck>.md` must show it as local, and `mdeck check <deck>.md` reports any mistake in its `extension.toml`.
148
148
  - **Hand over:** tell the user to run `mdeck design <deck>.md` (or `mdeck design <folder>` when there is no deck yet). It shows the look on a sample deck with every kind of slide, light and dark, and lets them fine-tune colours, fonts and sizes there.
@@ -26,6 +26,7 @@ export function checkDeck(slidesPath, registry, source = readFileSync(slidesPath
26
26
  for (const record of Object.values(registry.palettes)) {
27
27
  if (record.source !== 'local') continue
28
28
  for (const problem of paletteProblems(record.manifest)) diagnostics.push({ severity: 'warning', code: 'palette-contrast', message: `Palette "${record.id}", ${problem}`, line: 1, column: 1 })
29
+ for (const role of record.manifest.fallbacks ?? []) diagnostics.push({ severity: 'warning', code: 'palette-fallback', message: `Palette "${record.id}" sets no ${role}, so it gets mdeck's amber; give it a colour of its own in [light] and [dark]`, line: 1, column: 1 })
29
30
  }
30
31
  // The deck's drawings file: readable, and only for slides that still exist.
31
32
  const inkPath = inkFileFor(resolve(slidesPath))
package/src/cli/themes.js CHANGED
@@ -27,7 +27,7 @@ function looksOf(registry, catalogue) {
27
27
  const palettes = manifestsOf(registry, 'palette')
28
28
  // Each theme's icon for the gallery's list (see renderPreviews).
29
29
  const icons = new Map(catalogue.themes.map(entry => [entry.id, entry.icon]))
30
- const colours = tokens => Object.fromEntries(['--bg', '--surface', '--ink', '--accent', '--accent-2'].map(key => [key, tokens?.[key] ?? null]))
30
+ const colours = tokens => Object.fromEntries(['--bg', '--surface', '--ink', '--accent', '--accent-2', '--accent-3'].map(key => [key, tokens?.[key] ?? null]))
31
31
  return {
32
32
  mdeck: VERSION,
33
33
  themes: Object.values(manifestsOf(registry, 'theme')).map(theme => ({ id: theme.id, title: theme.title, description: theme.description ?? '', builtIn: builtIn.has(theme.id + ':t'),
@@ -1,11 +1,6 @@
1
- :root {
2
- --callout-note: #0969da;
3
- --callout-tip: #1a7f37;
4
- --callout-important: #8250df;
5
- --callout-warning: #eab308;
6
- --callout-caution: #cf222e;
7
- }
8
-
1
+ /* Callouts take the palette's accents by what they mean: a note, a tip and
2
+ something important are emphasis, in the accent; a warning and a caution
3
+ call for attention, in the third accent. */
9
4
  .callout {
10
5
  border-left: 4px solid var(--rule);
11
6
  padding: 20px 28px;
@@ -21,20 +16,15 @@
21
16
  }
22
17
  .callout p { margin: 0; color: var(--ink-soft); padding-left: 28px; }
23
18
 
24
- .callout-note { border-left-color: var(--callout-note); }
25
- .callout-tip { border-left-color: var(--callout-tip); }
26
- .callout-important { border-left-color: var(--callout-important); }
27
- .callout-warning { border-left-color: var(--callout-warning); }
28
- .callout-caution { border-left-color: var(--callout-caution); }
19
+ .callout:is(.callout-note, .callout-tip, .callout-important) { border-left-color: var(--accent); }
20
+ .callout:is(.callout-warning, .callout-caution) { border-left-color: var(--accent-3); }
29
21
 
30
- /* Titles are text: the signal colour mixed with the text colour, darker on
31
- light slides and lighter on dark ones, so they can be read on both. The
32
- yellow of a warning needs more of the text colour. */
33
- .callout-note .callout-title { color: color-mix(in oklab, var(--callout-note) 65%, var(--ink)); }
34
- .callout-tip .callout-title { color: color-mix(in oklab, var(--callout-tip) 65%, var(--ink)); }
35
- .callout-important .callout-title { color: color-mix(in oklab, var(--callout-important) 65%, var(--ink)); }
36
- .callout-warning .callout-title { color: color-mix(in oklab, var(--callout-warning) 45%, var(--ink)); }
37
- .callout-caution .callout-title { color: color-mix(in oklab, var(--callout-caution) 65%, var(--ink)); }
22
+ /* Titles are text: the accent mixed with the text colour, darker on light
23
+ slides and lighter on dark ones, so they can be read on both. The third
24
+ accent is a marker colour that need not be readable as text (a yellow, say),
25
+ so it gets more of the text colour. */
26
+ .callout:is(.callout-note, .callout-tip, .callout-important) .callout-title { color: color-mix(in oklab, var(--accent) 65%, var(--ink)); }
27
+ .callout:is(.callout-warning, .callout-caution) .callout-title { color: color-mix(in oklab, var(--accent-3) 45%, var(--ink)); }
38
28
 
39
29
  /* Blocks for lectures, in every theme. Theorems, lemmas and corollaries in
40
30
  the accent with the statement in italics, definitions in the second
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'preact/hooks'
3
3
  import { Icon } from '../../components/Icon.jsx'
4
4
 
5
5
  export function Swatches({ tokens = {} }) {
6
- return <span class="swatches">{['--bg', '--surface', '--rule', '--accent', '--accent-2', '--ink'].map(key => tokens[key] ? <i key={key} style={{ background: tokens[key] }} /> : null)}</span>
6
+ return <span class="swatches">{['--bg', '--surface', '--rule', '--accent', '--accent-2', '--accent-3', '--ink'].map(key => tokens[key] ? <i key={key} style={{ background: tokens[key] }} /> : null)}</span>
7
7
  }
8
8
 
9
9
  // The palettes a theme offers, each with its colours in the light or dark
@@ -7,8 +7,8 @@ export const ID_RE = /^[a-z][a-z0-9-]*$/
7
7
  export { COLOR_ROLES as CORE_TOKENS } from '../extensions/tokens.js'
8
8
  // Readable colours a new palette starts from.
9
9
  const STARTER_COLORS = {
10
- light: { '--bg': '#ffffff', '--surface': '#f2f2f2', '--ink': '#111111', '--ink-soft': '#333333', '--muted': '#666666', '--rule': '#dadada', '--accent': '#0b6bcb', '--accent-2': '#c2410c', '--on-accent': '#ffffff' },
11
- dark: { '--bg': '#111111', '--surface': '#1e1e1e', '--ink': '#f2f2f2', '--ink-soft': '#cfcfcf', '--muted': '#8f8f8f', '--rule': '#2e2e2e', '--accent': '#6aaeff', '--accent-2': '#fb923c', '--on-accent': '#111111' },
10
+ light: { '--bg': '#ffffff', '--surface': '#f2f2f2', '--ink': '#111111', '--ink-soft': '#333333', '--muted': '#666666', '--rule': '#dadada', '--accent': '#0b6bcb', '--accent-2': '#c2410c', '--accent-3': '#e0a100', '--on-accent': '#ffffff' },
11
+ dark: { '--bg': '#111111', '--surface': '#1e1e1e', '--ink': '#f2f2f2', '--ink-soft': '#cfcfcf', '--muted': '#8f8f8f', '--rule': '#2e2e2e', '--accent': '#6aaeff', '--accent-2': '#fb923c', '--accent-3': '#f5c542', '--on-accent': '#111111' },
12
12
  }
13
13
 
14
14
  const entries = table => Object.entries(table ?? {}).map(([name, value]) => ({ name, ...(value && typeof value === 'object' && !Array.isArray(value) ? value : { value }) }))
@@ -35,6 +35,8 @@ const RULES = [
35
35
  ['--muted', '--bg', 4.5, 'muted text'],
36
36
  ['--accent', '--bg', 3, 'the accent'],
37
37
  ['--accent-2', '--bg', 3, 'the second accent'],
38
+ // A marker (bars, shapes), not text: it has to be seen, not read.
39
+ ['--accent-3', '--bg', 1.5, 'the third accent'],
38
40
  ['--on-accent', '--accent', 3, 'text on the accent'],
39
41
  ]
40
42
 
@@ -48,7 +50,7 @@ export function paletteProblems(palette) {
48
50
  for (const appearance of ['light', 'dark']) {
49
51
  const tokens = palette[appearance] ?? {}
50
52
  for (const [fore, back, minimum, what] of RULES) {
51
- if (palette.theme && (fore === '--accent' || fore === '--accent-2')) continue
53
+ if (palette.theme && (fore === '--accent' || fore === '--accent-2' || fore === '--accent-3')) continue
52
54
  const ratio = contrast(tokens[fore], tokens[back])
53
55
  if (ratio != null && ratio < minimum) problems.push(`${appearance}: ${what} (${fore} on ${back}) has a contrast of ${ratio.toFixed(1)}:1; at least ${minimum}:1 is needed`)
54
56
  }
@@ -4,7 +4,7 @@
4
4
  import { existsSync } from 'node:fs'
5
5
  import { resolve, relative, isAbsolute, sep } from 'node:path'
6
6
  import { parse as parseToml, TomlError } from 'smol-toml'
7
- import { TOKEN_NAME_RE, COLOR_ROLES, APPEARANCES } from './tokens.js'
7
+ import { TOKEN_NAME_RE, COLOR_ROLES, COLOR_FALLBACKS, APPEARANCES } from './tokens.js'
8
8
  import { propertyErrors } from '../layouts/layoutProps.js'
9
9
 
10
10
  export const SCHEMA_VERSION = 1
@@ -247,12 +247,16 @@ function validateGuide(table, fail) {
247
247
  function validatePalette(raw, shared, fail) {
248
248
  if (raw.theme != null && (typeof raw.theme !== 'string' || !ID_RE.test(raw.theme))) fail('theme must be the id of the only theme that may use this palette', 'theme')
249
249
  const variants = {}
250
+ const fallbacks = new Set()
250
251
  for (const appearance of APPEARANCES) {
251
252
  if (!isPlainObject(raw[appearance])) fail(`a [${appearance}] table with the ${appearance} colours is required`, appearance)
252
253
  const tokens = validateTokens(raw[appearance], appearance, fail, { required: true })
253
- const missing = COLOR_ROLES.filter(role => !Object.hasOwn(tokens, role))
254
+ const missing = COLOR_ROLES.filter(role => !Object.hasOwn(tokens, role) && !COLOR_FALLBACKS[role])
254
255
  if (missing.length) fail(`missing colours: ${missing.join(', ')}`, appearance)
256
+ for (const [role, values] of Object.entries(COLOR_FALLBACKS)) {
257
+ if (!Object.hasOwn(tokens, role)) { tokens[role] = values[appearance]; fallbacks.add(role) }
258
+ }
255
259
  variants[appearance] = tokens
256
260
  }
257
- return { ...shared, ...(raw.theme ? { theme: raw.theme } : {}), light: variants.light, dark: variants.dark }
261
+ return { ...shared, ...(raw.theme ? { theme: raw.theme } : {}), light: variants.light, dark: variants.dark, ...(fallbacks.size ? { fallbacks: [...fallbacks] } : {}) }
258
262
  }
@@ -74,6 +74,9 @@ export function checkPackage({ kind, id, files }, { published = false } = {}) {
74
74
  }
75
75
  if (record.kind !== kind) fail(`${id}/extension.toml says kind = "${record.kind}", but it is in the ${kind}s`)
76
76
  if (published) for (const key of ['version', 'author', 'license']) if (!record.package?.[key]) fail(`The ${kind} ${id}: ${key} is required in the theme repository`)
77
+ // Palettes made before a colour existed get mdeck's fallback; a new one in
78
+ // the repository sets every colour itself.
79
+ if (published && record.manifest.fallbacks?.length) fail(`The palette ${id}: ${record.manifest.fallbacks.join(', ')} is required in the theme repository`)
77
80
  if (kind === 'theme') {
78
81
  for (const url of record.manifest.fonts) if (!FONT_HOSTS.some(host => url.startsWith(host))) fail(`The theme ${id} loads fonts from ${url}; allowed are ${FONT_HOSTS.join(', ')}`)
79
82
  checkCss(files['styles.css'] ?? '', `The theme ${id}: styles.css`)
@@ -9,8 +9,16 @@ export function tokensToCss(tokens = {}, selector = ':root') {
9
9
  }
10
10
 
11
11
  // The colours every palette variant defines. Themes use only these (and
12
- // mixes of them), so any palette fits any theme.
13
- export const COLOR_ROLES = ['--bg', '--surface', '--ink', '--ink-soft', '--muted', '--rule', '--accent', '--accent-2', '--on-accent']
12
+ // mixes of them), so any palette fits any theme. The three accents mean
13
+ // something: --accent is emphasis (and notes, tips, important callouts),
14
+ // --accent-2 its companion (gradients, a contrast to the accent), --accent-3
15
+ // attention (warnings and cautions), used sparingly; it marks things and
16
+ // need not be readable as text.
17
+ export const COLOR_ROLES = ['--bg', '--surface', '--ink', '--ink-soft', '--muted', '--rule', '--accent', '--accent-2', '--accent-3', '--on-accent']
18
+ // Colours a palette may leave out, and what it gets instead: palettes made
19
+ // before the third accent get an amber that calls for attention in light and
20
+ // dark alike (`mdeck check` suggests setting one's own).
21
+ export const COLOR_FALLBACKS = { '--accent-3': { light: '#d99a00', dark: '#f5c542' } }
14
22
  export const APPEARANCES = ['light', 'dark']
15
23
 
16
24
  /**
@@ -237,7 +237,7 @@ export function latestByDevice(messages) {
237
237
  // The deck's look as the answer page needs it: theme variables, font
238
238
  // stylesheets and the style of a slide heading, read from the page that shows
239
239
  // the slides (the presenter's current-slide frame, or the deck itself).
240
- const LOOK_TOKENS = ['--bg', '--ink', '--ink-soft', '--muted', '--rule', '--surface', '--accent', '--accent-2', '--on-accent', '--font-body', '--font-display']
240
+ const LOOK_TOKENS = ['--bg', '--ink', '--ink-soft', '--muted', '--rule', '--surface', '--accent', '--accent-2', '--accent-3', '--on-accent', '--font-body', '--font-display']
241
241
  let lookSource = () => document
242
242
  export function setLookSource(source) { lookSource = source }
243
243
  export function captureLook(doc = lookSource()) {
@@ -43,7 +43,7 @@ export const S = {
43
43
  }
44
44
 
45
45
  export function PaletteSwatches({ tokens }) {
46
- const keys = ['--bg', '--surface', '--rule', '--accent', '--accent-2', '--ink']
46
+ const keys = ['--bg', '--surface', '--rule', '--accent', '--accent-2', '--accent-3', '--ink']
47
47
  return (
48
48
  <div style={{ display: 'flex', gap: '2px', alignItems: 'center', flexShrink: 0 }}>
49
49
  {keys.map(k => tokens[k]