fb-slides 0.1.2 → 0.3.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/README.md CHANGED
@@ -151,6 +151,8 @@ export default {
151
151
  decks: 'decks', // folder of .md
152
152
  demos: 'demo', // folder behind a bare `<!-- demo: name -->`
153
153
  static: ['assets', 'demo'], // served and published; auto-detected when omitted
154
+ revealTheme: 'dracula', // one of reveal.js's own themes; omit for this one
155
+ webfonts: false, // let the reveal themes fetch their Google fonts
154
156
  theme: 'theme.css', // loaded after the base theme; auto when it exists
155
157
  favicon: 'assets/favicon.png',
156
158
 
@@ -193,6 +195,34 @@ rather than replaces. Almost everything hangs off the tokens in `:root`:
193
195
  }
194
196
  ```
195
197
 
198
+ ### reveal.js themes
199
+
200
+ The fifteen themes from [revealjs.com/themes](https://revealjs.com/themes/) are all in the
201
+ box — pick one by name and nothing else changes:
202
+
203
+ ```js
204
+ export default { revealTheme: 'dracula' };
205
+ ```
206
+
207
+ `beige` · `black` · `black-contrast` · `blood` · `dracula` · `league` · `moon` · `night` ·
208
+ `serif` · `simple` · `sky` · `solarized` · `white` · `white-contrast`
209
+
210
+ Only the chosen one is ever loaded — one `<link>`, ~7 KB — and the build copies that file
211
+ and nothing else. Two things happen beyond the link:
212
+
213
+ **It is served from the deck, not from a CDN.** Six of the themes open with
214
+ `@import url(https://fonts.googleapis.com/…)`. Those lines are stripped, so a talk still
215
+ renders with the wifi off; the theme falls back to the next font in its own stack. Pass
216
+ `webfonts: true` to let them through and get the typography of the previews exactly. The
217
+ font folders reveal ships itself — league-gothic, source-sans-pro — are copied into
218
+ `dist/` when the theme asks for them.
219
+
220
+ **The chrome follows the theme.** The navigator, the badge and the signature are not
221
+ `.reveal` elements, so a borrowed theme would leave a black panel down the side of a white
222
+ deck. Every reveal 5 theme declares its palette as `--r-*` custom properties, and the base
223
+ theme points its own tokens at them, so the whole page moves together. A `theme.css` in the
224
+ project still has the last word over both.
225
+
196
226
  ## Publishing
197
227
 
198
228
  ```bash
package/bin/fb-slides.mjs CHANGED
@@ -69,11 +69,12 @@ const run = async () => {
69
69
 
70
70
  const { loadConfig } = await import('../lib/config.mjs');
71
71
  const root = process.cwd();
72
- const config = await loadConfig(root, overrides());
72
+ const reload = () => loadConfig(root, overrides());
73
+ const config = await reload();
73
74
 
74
75
  if (command === 'dev') {
75
76
  const { dev } = await import('../lib/dev.mjs');
76
- return void (await dev(config, RUNTIME));
77
+ return void (await dev(config, RUNTIME, reload));
77
78
  }
78
79
 
79
80
  if (command === 'build') {
package/lib/build.mjs CHANGED
@@ -14,6 +14,7 @@ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
14
14
  import { assertUsable } from './config.mjs';
15
15
  import { listDecks } from './decks.mjs';
16
16
  import { renderIndex } from './render.mjs';
17
+ import { REVEAL_THEME_URL, readRevealTheme } from './theme.mjs';
17
18
  import { VENDOR_FILES, VENDOR_MOUNTS, packageDir } from './vendor.mjs';
18
19
 
19
20
  const copyFile = async (from, to) => {
@@ -59,6 +60,18 @@ export const build = async (config, runtimeDir, { quiet = false } = {}) => {
59
60
  await cp(from, join(out, dir), { recursive: true, filter: (src) => !excluded(src) });
60
61
  }
61
62
 
63
+ // 3b. the reveal theme, rewritten, with the font folders it imports — and
64
+ // only those: source-sans-pro is 1.8 MB and most themes never ask for it.
65
+ if (config.revealTheme) {
66
+ const { css, fonts, dir } = await readRevealTheme(config.revealTheme, { webfonts: config.webfonts });
67
+ await writeFile(join(out, REVEAL_THEME_URL), css);
68
+ for (const font of fonts) {
69
+ await cp(join(dir, 'fonts', font), join(out, 'vendor', VENDOR_MOUNTS['reveal.js'], 'dist/theme/fonts', font), {
70
+ recursive: true,
71
+ });
72
+ }
73
+ }
74
+
62
75
  // 4. loose root files the config names — a theme override, a favicon.
63
76
  for (const file of [config.theme, config.favicon].filter(Boolean)) {
64
77
  const from = resolve(config.root, file);
@@ -73,7 +86,8 @@ export const build = async (config, runtimeDir, { quiet = false } = {}) => {
73
86
  await writeFile(join(out, 'decks.json'), `${JSON.stringify(decks, null, 2)}\n`);
74
87
 
75
88
  if (!quiet) {
76
- console.log(` ${config.outDir}/ ${decks.length} decks · ${config.static.join(', ') || 'no static dirs'} · ${Date.now() - started}ms`);
89
+ const theme = config.revealTheme ? ` · theme ${config.revealTheme}` : '';
90
+ console.log(` ${config.outDir}/ ← ${decks.length} decks · ${config.static.join(', ') || 'no static dirs'}${theme} · ${Date.now() - started}ms`);
77
91
  }
78
92
  return { decks, out };
79
93
  };
package/lib/config.mjs CHANGED
@@ -6,10 +6,12 @@
6
6
  // needs running. A project that follows the default shape needs no config at all.
7
7
  // ---------------------------------------------------------------------------
8
8
 
9
- import { existsSync } from 'node:fs';
9
+ import { existsSync, statSync } from 'node:fs';
10
10
  import { basename, join, resolve } from 'node:path';
11
11
  import { pathToFileURL } from 'node:url';
12
12
 
13
+ import { assertRevealTheme } from './theme.mjs';
14
+
13
15
  const CONFIG_FILES = ['slides.config.js', 'slides.config.mjs', 'slides.config.json'];
14
16
 
15
17
  // Directories a project gets served and published for free when it has them, so
@@ -22,7 +24,10 @@ export const ALWAYS_EXCLUDED = ['node_modules', '.git', '.angular', '.next', '.c
22
24
  export const findConfigFile = (root) => CONFIG_FILES.map((name) => join(root, name)).find(existsSync) ?? null;
23
25
 
24
26
  const importConfig = async (file) => {
25
- const url = pathToFileURL(file).href;
27
+ // ESM caches a module by URL for the life of the process, so the dev server's
28
+ // second read of an edited config would be the first one again. The mtime in
29
+ // the query makes it a different URL exactly when the file has changed.
30
+ const url = `${pathToFileURL(file).href}?t=${statSync(file).mtimeMs}`;
26
31
  const module = file.endsWith('.json')
27
32
  ? await import(url, { with: { type: 'json' } })
28
33
  : await import(url);
@@ -53,6 +58,14 @@ export const loadConfig = async (root = process.cwd(), overrides = {}) => {
53
58
  decksDir,
54
59
  demosDir,
55
60
  static: statics,
61
+ // One of reveal's own fifteen — 'dracula', 'sky', 'white'. It lands after
62
+ // the base theme and takes the slides; the chrome follows it through the
63
+ // `--r-*` variables every reveal 5 theme declares. Null: this package's look.
64
+ revealTheme: user.revealTheme ?? null,
65
+ // Six of those themes pull their typefaces from fonts.googleapis.com. Off by
66
+ // default — the deck is served whole or not at all — at the cost of falling
67
+ // back to the theme's second-choice font stack.
68
+ webfonts: user.webfonts ?? false,
56
69
  // The project's own CSS, loaded *after* the base theme: an override, not a
57
70
  // replacement. `theme.css` at the root is picked up without being declared.
58
71
  theme: user.theme ?? (existsSync(join(root, 'theme.css')) ? 'theme.css' : null),
@@ -72,6 +85,10 @@ export const loadConfig = async (root = process.cwd(), overrides = {}) => {
72
85
  exclude: [...ALWAYS_EXCLUDED, overrides.outDir ?? user.outDir ?? 'dist', ...(user.exclude ?? [])],
73
86
  };
74
87
 
88
+ // Named early: a typo in the config should be a message, not a deck that
89
+ // silently wears the wrong clothes.
90
+ if (config.revealTheme) await assertRevealTheme(config.revealTheme);
91
+
75
92
  config.decksPath = resolve(root, config.decksDir);
76
93
  config.outPath = resolve(root, config.outDir);
77
94
  // What the browser asks for, as opposed to where it is on disk.
package/lib/dev.mjs CHANGED
@@ -13,6 +13,7 @@ import { join, resolve } from 'node:path';
13
13
  import { assertUsable } from './config.mjs';
14
14
  import { createDeckServer, listen } from './server.mjs';
15
15
  import { renderIndex } from './render.mjs';
16
+ import { REVEAL_THEME_URL, readRevealTheme } from './theme.mjs';
16
17
  import { VENDOR_MOUNTS, packageDir } from './vendor.mjs';
17
18
 
18
19
  const OPEN = { darwin: 'open', win32: 'start' };
@@ -52,8 +53,25 @@ const startSideServer = (spec, root, children) => {
52
53
  });
53
54
  };
54
55
 
55
- export const dev = async (config, runtimeDir) => {
56
+ // What the page is built from, read again on every request. A config with a
57
+ // syntax error in it — the state it is in halfway through an edit — leaves the
58
+ // last good one standing rather than serving a broken deck.
59
+ const reader = (config, reload) => {
60
+ let good = config;
61
+ return async () => {
62
+ if (!reload) return good;
63
+ try {
64
+ good = await reload();
65
+ } catch (error) {
66
+ console.warn(` ⚠ slides.config: ${error.message}`);
67
+ }
68
+ return good;
69
+ };
70
+ };
71
+
72
+ export const dev = async (config, runtimeDir, reload) => {
56
73
  assertUsable(config);
74
+ const current = reader(config, reload);
57
75
 
58
76
  const mounts = [
59
77
  // The project first: a file next to the decks shadows the one this package
@@ -69,8 +87,18 @@ export const dev = async (config, runtimeDir) => {
69
87
  const server = createDeckServer({
70
88
  mounts,
71
89
  decksPath: config.decksPath,
72
- // Rendered per request: editing slides.config.js and reloading is enough.
73
- index: () => renderIndex(config, runtimeDir),
90
+ // Rendered per request from a config read per request: editing the title,
91
+ // the theme or the signature and reloading is enough. What was fixed when
92
+ // the server came up — the mounts, the port, the side processes — still
93
+ // needs a restart.
94
+ index: async () => renderIndex(await current(), runtimeDir),
95
+ generated: {
96
+ [`/${REVEAL_THEME_URL}`]: async () => {
97
+ const now = await current();
98
+ if (!now.revealTheme) return '/* no revealTheme in slides.config.js */';
99
+ return (await readRevealTheme(now.revealTheme, { webfonts: now.webfonts })).css;
100
+ },
101
+ },
74
102
  });
75
103
 
76
104
  await listen(server, config.port);
package/lib/render.mjs CHANGED
@@ -7,6 +7,8 @@
7
7
  import { readFile } from 'node:fs/promises';
8
8
  import { join } from 'node:path';
9
9
 
10
+ import { REVEAL_THEME_URL } from './theme.mjs';
11
+
10
12
  const escapeHtml = (value) =>
11
13
  String(value).replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
12
14
 
@@ -28,6 +30,9 @@ const signatureHtml = (signature) => {
28
30
  const headHtml = (config) => {
29
31
  const tags = [];
30
32
  if (config.favicon) tags.push(`<link rel="icon" href="${escapeHtml(config.favicon)}" />`);
33
+ // After the base theme, before the project's: reveal's theme dresses the
34
+ // slides, and whatever the project says still has the last word.
35
+ if (config.revealTheme) tags.push(`<link rel="stylesheet" href="${REVEAL_THEME_URL}" />`);
31
36
  // After the base theme on purpose: the project's CSS overrides, it does not replace.
32
37
  if (config.theme) tags.push(`<link rel="stylesheet" href="${escapeHtml(config.theme)}" />`);
33
38
  return tags.join('\n ');
@@ -43,7 +48,12 @@ export const renderIndex = async (config, runtimeDir) => {
43
48
  fragmentLists: config.fragmentLists,
44
49
  };
45
50
 
51
+ // The chrome reads this to take its colours from the reveal theme instead of
52
+ // the base one — see the bridge at the top of theme.base.css.
53
+ const htmlAttrs = config.revealTheme ? ` data-reveal-theme="${escapeHtml(config.revealTheme)}"` : '';
54
+
46
55
  return template
56
+ .replaceAll('{{htmlAttrs}}', htmlAttrs)
47
57
  .replaceAll('{{lang}}', escapeHtml(config.lang))
48
58
  .replaceAll('{{title}}', escapeHtml(config.title))
49
59
  .replaceAll('{{head}}', headHtml(config))
package/lib/server.mjs CHANGED
@@ -99,7 +99,9 @@ const sendFile = (req, res, path, size) => {
99
99
  };
100
100
 
101
101
  // mounts: [{ prefix: '/', dir }] — checked in order, first hit wins.
102
- export const createDeckServer = ({ mounts, index, decksPath }) => {
102
+ // generated: { '/path.css': async () => body } files this package rewrites
103
+ // rather than serves, checked before any mount so node_modules cannot shadow one.
104
+ export const createDeckServer = ({ mounts, index, decksPath, generated = {} }) => {
103
105
  const handler = async (req, res) => {
104
106
  if (req.method !== 'GET' && req.method !== 'HEAD') return send(res, 405, 'method not allowed');
105
107
 
@@ -120,6 +122,10 @@ export const createDeckServer = ({ mounts, index, decksPath }) => {
120
122
  return send(res, 200, body, { 'Content-Type': MIME['.json'] });
121
123
  }
122
124
 
125
+ if (generated[pathname]) {
126
+ return send(res, 200, await generated[pathname](), { 'Content-Type': mime(pathname) });
127
+ }
128
+
123
129
  if (DENIED.test(pathname)) return send(res, 403, 'forbidden', { 'Content-Type': MIME['.txt'] });
124
130
 
125
131
  for (const { prefix, dir } of mounts) {
package/lib/theme.mjs ADDED
@@ -0,0 +1,57 @@
1
+ // ---------------------------------------------------------------------------
2
+ // reveal.js ships fifteen themes; a deck wears one of them, or none.
3
+ //
4
+ // They are not linked straight out of node_modules. Six of the fifteen open with
5
+ // `@import url(https://fonts.googleapis.com/…)`, which is a third-party request
6
+ // on every load — and a talk that has to survive the conference wifi cannot rest
7
+ // on one. The file is rewritten instead: the remote imports go, the font folders
8
+ // reveal already ships locally stay, and what the browser gets is served from
9
+ // this project like everything else.
10
+ //
11
+ // The rewritten file is generated per request in dev and written once by build,
12
+ // at the same URL either way — index.html never learns which of the two it is.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ import { readdir, readFile } from 'node:fs/promises';
16
+ import { basename, join } from 'node:path';
17
+
18
+ import { VENDOR_MOUNTS, packageDir } from './vendor.mjs';
19
+
20
+ export const REVEAL_THEME_URL = 'reveal-theme.css';
21
+
22
+ const themeDir = () => join(packageDir('reveal.js'), 'dist', 'theme');
23
+
24
+ // The theme is served from the site root, so reveal's own `./fonts/…` would
25
+ // resolve one folder too high: point it at the vendor mount instead.
26
+ const FONT_BASE = `vendor/${VENDOR_MOUNTS['reveal.js']}/dist/theme/fonts/`;
27
+
28
+ const REMOTE_IMPORT = /@import\s+url\(\s*['"]?https?:[^)]*\)\s*;?[ \t]*\n?/gi;
29
+ const LOCAL_FONT_URL = /url\(\s*['"]?\.\/fonts\//gi;
30
+
31
+ export const listRevealThemes = async () =>
32
+ (await readdir(themeDir()))
33
+ .filter((file) => file.endsWith('.css'))
34
+ .map((file) => basename(file, '.css'))
35
+ .sort();
36
+
37
+ export const assertRevealTheme = async (name) => {
38
+ const themes = await listRevealThemes();
39
+ if (themes.includes(name)) return name;
40
+ throw new Error(
41
+ `unknown revealTheme: ${name}\n` +
42
+ ` reveal.js ships: ${themes.join(', ')}\n` +
43
+ ` drop the key for this package's own theme`,
44
+ );
45
+ };
46
+
47
+ // The font folders the chosen theme actually imports. build copies these and
48
+ // nothing else: source-sans-pro alone is 1.8 MB, and most themes never ask for it.
49
+ const fontDirs = (css) => [...new Set([...css.matchAll(/\.\/fonts\/([^/]+)\//g)].map((m) => m[1]))];
50
+
51
+ // `webfonts: true` keeps the Google Fonts imports — the exact typography of
52
+ // revealjs.com/themes, at the cost of the offline guarantee.
53
+ export const readRevealTheme = async (name, { webfonts = false } = {}) => {
54
+ const source = await readFile(join(themeDir(), `${name}.css`), 'utf8');
55
+ const css = (webfonts ? source : source.replace(REMOTE_IMPORT, '')).replace(LOCAL_FONT_URL, `url(${FONT_BASE}`);
56
+ return { css, fonts: fontDirs(source), dir: themeDir() };
57
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fb-slides",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Markdown-driven reveal.js decks: live demo embeds, annotation, mermaid, and a zero-config dev server",
6
6
  "keywords": [
@@ -25,9 +25,11 @@ const COLOURS = [
25
25
  { label: 'Black', glow: '#000000', core: '#000000' },
26
26
  ];
27
27
 
28
- // A released stroke rubs itself out from its oldest point towards its newest.
29
- // FADE_MS is how long that takes end to end, FADE_TAIL how much of the stroke
30
- // the erase front softens as it passes — at 0 it would be a hard wipe.
28
+ // A released stroke holds on screen for HOLD_MS, then rubs itself out from its
29
+ // oldest point towards its newest. FADE_MS is how long the erase takes end to
30
+ // end, FADE_TAIL how much of the stroke the erase front softens as it passes —
31
+ // at 0 it would be a hard wipe.
32
+ const HOLD_MS = 2000;
31
33
  const FADE_MS = 1600;
32
34
  const FADE_TAIL = 0.35;
33
35
 
@@ -210,6 +212,7 @@ const RevealAnnotate = () => ({
210
212
  const strokes = [];
211
213
  let drawing = null;
212
214
  let frame = 0;
215
+ let holdTimer = 0;
213
216
 
214
217
  const addPoint = (event) => {
215
218
  const points = drawing.points;
@@ -284,7 +287,12 @@ const RevealAnnotate = () => ({
284
287
 
285
288
  // Returns false once the stroke has faded out and can be dropped.
286
289
  const paint = (stroke, now) => {
287
- const progress = stroke.releasedAt === null ? null : (now - stroke.releasedAt) / FADE_MS;
290
+ // Clamped at 0 through the hold: a stroke waiting its turn paints exactly
291
+ // like one still under the pen, only released.
292
+ const progress =
293
+ stroke.releasedAt === null
294
+ ? null
295
+ : Math.max(0, (now - stroke.releasedAt - HOLD_MS) / FADE_MS);
288
296
  if (progress !== null && progress >= 1) return false;
289
297
 
290
298
  const box = boundsOf(stroke.points);
@@ -334,18 +342,27 @@ const RevealAnnotate = () => ({
334
342
  if (!paint(strokes[i], now)) strokes.splice(i, 1);
335
343
  }
336
344
  // Only a fading stroke needs the next frame. A live one changes when a
337
- // point is added and not before, so a pen held still costs nothing —
338
- // without this the loop repaints a full-screen canvas forever.
339
- if (strokes.some((stroke) => stroke.releasedAt !== null)) schedule();
345
+ // point is added and not before, and a released one sits untouched
346
+ // through its hold for those a single timeout wakes the loop when the
347
+ // first fade is due, instead of repainting a full-screen canvas forever.
348
+ const waits = strokes
349
+ .filter((stroke) => stroke.releasedAt !== null)
350
+ .map((stroke) => stroke.releasedAt + HOLD_MS - now);
351
+ if (waits.some((wait) => wait <= 0)) schedule();
352
+ else if (waits.length) holdTimer = setTimeout(schedule, Math.min(...waits));
340
353
  };
341
354
 
342
355
  const schedule = () => {
356
+ clearTimeout(holdTimer);
357
+ holdTimer = 0;
343
358
  frame ||= requestAnimationFrame(render);
344
359
  };
345
360
 
346
361
  const clear = () => {
347
362
  drawing = null;
348
363
  strokes.length = 0;
364
+ clearTimeout(holdTimer);
365
+ holdTimer = 0;
349
366
  ctx.clearRect(0, 0, width, height);
350
367
  };
351
368
 
@@ -427,14 +444,21 @@ const RevealAnnotate = () => ({
427
444
  deck.addKeyBinding({ keyCode: KEY_CODE, key: 'P', description: 'Toggle the pen' }, () =>
428
445
  setPen(!penOn),
429
446
  );
430
- // Escape is reveal's own overview toggle, and it binds before a plugin can.
431
- // Capture phase gets there first but only to take Escape back while the
432
- // pen is armed, so the key still opens the overview the rest of the time.
447
+ // Keys the pen borrows only while it is armed, taken in capture phase so
448
+ // reveal's own bindings never see them: Escape (otherwise the overview
449
+ // toggle) disarms it, and the number row picks its ink 1 to 6, in the
450
+ // order the swatches sit in the toolbar. The rest of the time both fall
451
+ // through untouched.
433
452
  document.addEventListener(
434
453
  'keydown',
435
454
  (event) => {
436
- if (event.key !== 'Escape' || !penOn) return;
437
- setPen(false);
455
+ if (!penOn || event.metaKey || event.ctrlKey || event.altKey) return;
456
+ if (event.key === 'Escape') setPen(false);
457
+ else {
458
+ const index = '123456'.indexOf(event.key);
459
+ if (index === -1 || index >= COLOURS.length) return;
460
+ setInk(COLOURS[index]);
461
+ }
438
462
  event.stopPropagation();
439
463
  event.preventDefault();
440
464
  },
@@ -1,5 +1,5 @@
1
1
  <!doctype html>
2
- <html lang="{{lang}}">
2
+ <html lang="{{lang}}"{{htmlAttrs}}>
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -29,6 +29,39 @@
29
29
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
30
30
  }
31
31
 
32
+ /* --- wearing one of reveal's own themes ----------------------------------- */
33
+
34
+ /* `revealTheme:` in slides.config.js loads reveal's theme after this file, where
35
+ it takes the slides. The chrome — the navigator, the badge, the signature —
36
+ is not `.reveal`-scoped, so it would sail past untouched and leave a black
37
+ panel down the side of a white deck.
38
+
39
+ Every reveal 5 theme declares its palette as `--r-*` custom properties, so
40
+ the fix is one indirection rather than fifteen stylesheets: point the tokens
41
+ above at reveal's, and the whole of the chrome follows whatever the deck
42
+ wears. The four reveal has no equivalent for are mixed from the two that
43
+ carry the theme, which keeps the panel's depth on a light theme as well as
44
+ a dark one. */
45
+ html[data-reveal-theme] {
46
+ --bg: var(--r-background-color);
47
+ --text: var(--r-main-color);
48
+ --accent: var(--r-link-color);
49
+ --accent-2: var(--r-link-color-hover, var(--r-link-color));
50
+ --sans: var(--r-main-font);
51
+ --mono: var(--r-code-font);
52
+ --bg-2: color-mix(in srgb, var(--r-background-color) 92%, var(--r-main-color));
53
+ --card: color-mix(in srgb, var(--r-background-color) 86%, var(--r-main-color));
54
+ --line: color-mix(in srgb, var(--r-main-color) 20%, transparent);
55
+ --muted: color-mix(in srgb, var(--r-main-color) 62%, var(--r-background-color));
56
+ }
57
+
58
+ /* The base look paints two dark halos on <html>, below the deck. They are the
59
+ wrong atmosphere for a borrowed theme, and on a light one they show through
60
+ wherever the viewport does not reach — so the theme's own flat colour takes
61
+ over. <body> is left alone on purpose: reveal paints the deck's background
62
+ there, and that is the theme's job now. */
63
+ html[data-reveal-theme] { background: var(--bg); }
64
+
32
65
  /* reveal.js 5 paints the page background on `.reveal-viewport` (the <html>
33
66
  element), so setting it on <body> would sit behind it and never show. */
34
67
  html,
@@ -111,6 +144,7 @@ body,
111
144
  /* Trailing margin would push centred content off-centre. */
112
145
  .reveal .slides section > :last-child { margin-bottom: 0; }
113
146
  .reveal strong { color: #fff; font-weight: 650; }
147
+ html[data-reveal-theme] .reveal strong { color: var(--r-heading-color, var(--text)); }
114
148
  .reveal em { color: var(--accent-2); font-style: italic; }
115
149
  .reveal a { color: var(--accent); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); }
116
150
 
@@ -9,6 +9,18 @@ export default {
9
9
  // Folder behind a bare `<!-- demo: counter -->` marker in the Markdown.
10
10
  demos: 'demo',
11
11
 
12
+ // One of reveal.js's own themes, served from this project rather than from a
13
+ // CDN: beige, black, black-contrast, blood, dracula, league, moon, night,
14
+ // serif, simple, sky, solarized, white, white-contrast. Uncomment to wear one
15
+ // — the deck's chrome follows it. Leave it out for this package's own look.
16
+ // revealTheme: 'dracula',
17
+
18
+ // Six of those themes ask fonts.googleapis.com for their typefaces. Those
19
+ // requests are stripped out, so a deck never depends on the room's wifi; the
20
+ // theme falls back to the next font in its own stack. Set this to true to let
21
+ // them through and get the typography of revealjs.com/themes exactly.
22
+ // webfonts: true,
23
+
12
24
  // Port `fb-slides dev` serves the deck on (`preview` uses the next one up).
13
25
  // `--port n` on the command line wins over this.
14
26
  port: 4000,